后缀数组
后缀数组
bzoj1031 JSOI字符加密Cipher
题目大意:给一个字符串,圈成圆圈,从任意位置断开,组成len个字符串,按字典序升序排序后,输出尾字母。
思路:将字符串加倍后,对所有后缀排序,用后缀数组的思想,O(nlogn),输出的时候只要输出长度>=len的相应位置的字母就可以了。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define maxnode 200005 using namespace std; int t1[maxnode]={0},t2[maxnode]={0},sa[maxnode]={0},cc[maxnode]={0},n,m=0; char ss[maxnode]; bool cmp(int *y,int a,int b,int k) { int a2,b2; a2= a+k>=n ? -1 : y[a+k]; b2= b+k>=n ? -1 : y[b+k]; a=y[a];b=y[b]; return a==b&&a2==b2; } void build() { int i,k,p;int *x=t1;int *y=t2; for (i=0;i<m;++i) cc[i]=0; for (i=0;i<n;++i) ++cc[x[i]=ss[i]]; for (i=1;i<m;++i) cc[i]+=cc[i-1]; for (i=n-1;i>=0;--i) sa[--cc[x[i]]]=i; for (k=1;k<=n;k<<=1) { p=0; for (i=n-k;i<n;++i) y[p++]=i; for (i=0;i<n;++i) if (sa[i]>=k) y[p++]=sa[i]-k; for (i=0;i<m;++i) cc[i]=0; for (i=0;i<n;++i) ++cc[x[y[i]]]; for (i=1;i<m;++i) cc[i]+=cc[i-1]; for (i=n-1;i>=0;--i) sa[--cc[x[y[i]]]]=y[i]; swap(x,y);m=1;x[sa[0]]=0; for (i=1;i<n;++i) x[sa[i]]=cmp(y,sa[i],sa[i-1],k) ? m-1 : m++; if (m>=n) break; } } int main() { int i,n1; scanf("%s",&ss);n1=strlen(ss); for (i=0;i<n1-1;++i) ss[i+n1]=ss[i]; n=n1*2-1; for (i=0;i<n1;++i) m=max(m,(int)ss[i]); ++m;build(); for (i=0;i<n;++i) if (sa[i]<n1) printf("%c",ss[sa[i]+n1-1]); printf("\n"); }
poj2406Power Strings
题目大意:求给定字符串最多能被一个字符串重复几次得到。
思路:虽然在学习后缀数组,不过第一反应是用kmp的思想,用失配数组进行操作。
#include<iostream> #include<cstring> #include<algorithm> #include<cstdio> #define maxnode 1000005 using namespace std; char ss[maxnode]; int f[maxnode]={0}; int main() { int i,j,l; while(scanf("%s",&ss)==1) { l=strlen(ss);if (l==1&&ss[0]=='.') break; f[0]=f[1]=0; for (i=1;i<l;++i) { j=f[i]; while(j&&ss[i]!=ss[j]) j=f[j]; f[i+1]= ss[i]==ss[j] ? j+1 : 0; } if (f[l]>0&&l%(l-f[l])==0) printf("%d\n",l/(l-f[l])); else printf("1\n"); } }
poj2774Long Long Message
题目大意:求两个字符串的最长连续公共字串。
思路:将两个字符串s1,s2连在一起,中间用一个特殊的符号连接(我用的是‘$’),求出满足起点在特殊符号两边、并且公共前缀最长的答案就可以了。求这个答案的时候,首先按height建立线段树,然后记录在以排名为下标的sa数组中,每一个串s1对应的后缀的前一个和后一个为串s2对应的后缀的排名,穷举每一个s1串的后缀,然后在线段树中查询之前记录下的min(前+1~rank(i))和min(rank(i)+1~后)中取较小值(这里还要和n-i取较小值),这里一定有最大的最小值在这一前一后之间取得。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 200005 #define inf 2100000000LL using namespace std; char s1[maxnode],s2[maxnode],ss[maxnode]; int t1[maxnode]={0},t2[maxnode]={0},cc[maxnode]={0},n,m=0,sa[maxnode]={0},rank[maxnode]={0},height[maxnode]={0},tree[maxnode*4]={0}, ml[maxnode]={0},mr[maxnode]={0}; bool cmp(int *y,int a,int b,int k) { int a2,b2; a2= a+k>=n ? -1 : y[a+k]; b2= b+k>=n ? -1 : y[b+k]; a=y[a];b=y[b]; return a==b&&a2==b2; } void buildt(int i,int l,int r) { int mid; if (l==r){tree[i]=height[l];return;} mid=(l+r)/2;buildt(i*2,l,mid);buildt(i*2+1,mid+1,r); tree[i]=min(tree[i*2],tree[i*2+1]); } int task(int i,int l,int r,int ll,int rr) { int mid,minn=inf; if (ll<=l&&r<=rr) return tree[i]; mid=(l+r)/2; if (ll<=mid) minn=min(minn,task(i*2,l,mid,ll,rr)); if (rr>mid) minn=min(minn,task(i*2+1,mid+1,r,ll,rr)); return minn; } void build() { int i,k,p;int *x=t1;int *y=t2; for (i=0;i<m;++i) cc[i]=0; for (i=0;i<n;++i) ++cc[x[i]=ss[i]]; for (i=1;i<m;++i) cc[i]+=cc[i-1]; for (i=n-1;i>=0;--i) sa[--cc[x[i]]]=i; for (k=1;k<=n;k<<=1) { p=0; for (i=n-k;i<n;++i) y[p++]=i; for (i=0;i<n;++i) if (sa[i]>=k) y[p++]=sa[i]-k; for (i=0;i<m;++i) cc[i]=0; for (i=0;i<n;++i) ++cc[x[y[i]]]; for (i=1;i<m;++i) cc[i]+=cc[i-1]; for (i=n-1;i>=0;--i) sa[--cc[x[y[i]]]]=y[i]; swap(x,y);x[sa[0]]=0;m=1; for (i=1;i<n;++i) x[sa[i]]=cmp(y,sa[i],sa[i-1],k) ? m-1 : m++; if (m>=n) break; } } void pre() { int i,j,k=0; for (i=0;i<n;++i) rank[sa[i]]=i; for (i=0;i<n;++i) { if (!rank[i]) continue; if (k) --k; j=sa[rank[i]-1]; while(ss[i+k]==ss[j+k]) ++k; height[rank[i]]=k; } } int main() { int i,j,n1,n2,ans=0; scanf("%s%s",&s1,&s2);n1=strlen(s1);n2=strlen(s2);n=n1+n2+1; for (i=0;i<n1;++i) ss[i]=s1[i]; ss[n1]='$'; for (i=n1+1;i<n;++i) ss[i]=s2[i-n1-1]; for (i=0;i<n;++i) m=max(m,(int)ss[i]); ++m;build();pre();buildt(1,0,n-1); j=-1; for (i=0;i<n;++i) { if (sa[i]>n1) j=i; else ml[sa[i]]=j; } j=-1; for (i=n-1;i>=0;--i) { if (sa[i]>n1) j=i; else mr[sa[i]]=j; } for (i=0;i<n1;++i) { if (n1-i<=ans) break; if (ml[i]!=-1) ans=max(ans,min(n1-i,task(1,0,n-1,ml[i]+1,rank[i]))); if (mr[i]!=-1) ans=max(ans,min(n1-i,task(1,0,n-1,rank[i]+1,mr[i]))); } printf("%d\n",ans); }
其实完全没必要再这么麻烦,我们只要从头扫一遍height数组,找到满足要求的最大的值就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 200005 #define inf 2100000000LL using namespace std; char s1[maxnode],s2[maxnode],ss[maxnode]; int t1[maxnode]={0},t2[maxnode]={0},cc[maxnode]={0},n,m=0,sa[maxnode]={0},rank[maxnode]={0},height[maxnode]={0}; bool cmp(int *y,int a,int b,int k) { int a2,b2; a2= a+k>=n ? -1 : y[a+k]; b2= b+k>=n ? -1 : y[b+k]; a=y[a];b=y[b]; return a==b&&a2==b2; } void build() { int i,k,p;int *x=t1;int *y=t2; for (i=0;i<m;++i) cc[i]=0; for (i=0;i<n;++i) ++cc[x[i]=ss[i]]; for (i=1;i<m;++i) cc[i]+=cc[i-1]; for (i=n-1;i>=0;--i) sa[--cc[x[i]]]=i; for (k=1;k<=n;k<<=1) { p=0; for (i=n-k;i<n;++i) y[p++]=i; for (i=0;i<n;++i) if (sa[i]>=k) y[p++]=sa[i]-k; for (i=0;i<m;++i) cc[i]=0; for (i=0;i<n;++i) ++cc[x[y[i]]]; for (i=1;i<m;++i) cc[i]+=cc[i-1]; for (i=n-1;i>=0;--i) sa[--cc[x[y[i]]]]=y[i]; swap(x,y);x[sa[0]]=0;m=1; for (i=1;i<n;++i) x[sa[i]]=cmp(y,sa[i],sa[i-1],k) ? m-1 : m++; if (m>=n) break; } } void pre() { int i,j,k=0; for (i=0;i<n;++i) rank[sa[i]]=i; for (i=0;i<n;++i) { if (!rank[i]) continue; if (k) --k; j=sa[rank[i]-1]; while(ss[i+k]==ss[j+k]) ++k; height[rank[i]]=k; } } int main() { int i,j,n1,n2,ans=0; scanf("%s%s",&s1,&s2);n1=strlen(s1);n2=strlen(s2);n=n1+n2+1; for (i=0;i<n1;++i) ss[i]=s1[i]; ss[n1]='$'; for (i=n1+1;i<n;++i) ss[i]=s2[i-n1-1]; for (i=0;i<n;++i) m=max(m,(int)ss[i]); ++m;build();pre(); for (i=1;i<n;++i) if ((sa[i-1]<n1&&sa[i]>n1)||(sa[i-1]>n1&&sa[i]<n1)) ans=max(ans,height[i]); printf("%d\n",ans); }
bzoj3238差异
题目大意:求sigma(lenTi+lenTj-2*lcp(Ti,Tj)),1<=i<j<=n,Ti表示从第i个字符开始的后缀,小标从1开始。
思路:将sigma拆开,可以发现主要就是求2*lcp(Ti,Tj),先用后缀数组构建height,然后建立线段树,保存最小值和最小值的位置,分治处理,对区间[l,r],找线段树中[l+1,r](下表一定注意,因为height[i]表示i和i-1的lcp长度)的最小值minn和位置minp,给ans减去(minn*(minp-l)*(r-minp+1))(还是要十分注意下标),然后在把sigma中其他部分加起来就行了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 500005 #define inf 2100000000LL #define LL long long using namespace std; struct use{ int minn,minp; }tree[maxnode*4]={0}; char ss[maxnode]={0}; int t1[maxnode]={0},t2[maxnode]={0},cc[maxnode]={0},sa[maxnode]={0},rank[maxnode]={0},height[maxnode]={0},n,m=0; long long ans=0; bool cmp(int *y,int a,int b,int k) { int a2,b2; a2= a+k>=n ? -1 : y[a+k]; b2= b+k>=n ? -1 : y[b+k]; a=y[a];b=y[b]; return a==b&&a2==b2; } void build() { int i,k,p; int *x=t1;int *y=t2; for (i=0;i<m;++i) cc[i]=0; for (i=0;i<n;++i) ++cc[x[i]=ss[i]]; for (i=1;i<m;++i) cc[i]+=cc[i-1]; for (i=n-1;i>=0;--i) sa[--cc[x[i]]]=i; for (k=1;k<=n;k<<=1) { p=0; for (i=n-k;i<n;++i) y[p++]=i; for (i=0;i<n;++i) if (sa[i]>=k) y[p++]=sa[i]-k; for (i=0;i<m;++i) cc[i]=0; for (i=0;i<n;++i) ++cc[x[y[i]]]; for (i=1;i<m;++i) cc[i]+=cc[i-1]; for (i=n-1;i>=0;--i) sa[--cc[x[y[i]]]]=y[i]; swap(x,y);m=1;x[sa[0]]=0; for (i=1;i<n;++i) x[sa[i]]=cmp(y,sa[i],sa[i-1],k) ? m-1 : m++; if (m>=n) break; } } void pre() { int i,j,k=0; for (i=0;i<n;++i) rank[sa[i]]=i; for (i=0;i<n;++i) { if (!rank[i]) continue; if (k) --k; j=sa[rank[i]-1]; while(ss[i+k]==ss[j+k]) ++k; height[rank[i]]=k; } } use updata(use x,use y){return x.minn<=y.minn ? x : y;} void buildt(int i,int l,int r) { int mid; if (l==r){tree[i].minn=height[l];tree[i].minp=l;return;}; mid=(l+r)/2; buildt(i*2,l,mid);buildt(i*2+1,mid+1,r); tree[i]=updata(tree[i*2],tree[i*2+1]); } use task(int i,int l,int r,int ll,int rr) { use x1,x2; int mid; if (ll<=l&&r<=rr) return tree[i]; mid=(l+r)/2;x1.minn=x2.minn=inf; if (ll<=mid) x1=task(i*2,l,mid,ll,rr); if (rr>mid) x2=task(i*2+1,mid+1,r,ll,rr); return updata(x1,x2); } void work(int l,int r) { use xx; if (l>=r) return; xx=task(1,1,n-1,l+1,r); work(l,xx.minp-1);work(xx.minp,r); ans-=2*(LL)xx.minn*(LL)(xx.minp-l)*(LL)(r-xx.minp+1); } int main() { int i,j; scanf("%s",&ss);n=strlen(ss); for (i=0;i<n;++i) m=max(m,(int)ss[i]); ++m;build();pre();buildt(1,1,n-1); for (i=0;i<n;++i) ans+=(LL)(n-1)*(LL)(n-i); work(0,n-1);printf("%lld\n",ans); }
bzoj2251 外星联络
题目大意:求出一个01串中出现次数多于一次的字串的个数(按字典序排序)。
思路:用后缀数组做出height后,所有后缀都排好序了,那么靠前面并且长度小的就是字典序小的。但是这里要保证不重复并且超过两次出现,所以我们每次从这一位的height[i]+1的长度到这个后缀长度开始往后找,只要height[j]>height[i]+1就一直往后,这样保证了开头是单调递增的长度也单调,所以答案就是字典序排好的了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 3005 using namespace std; int sa[maxnode],t1[maxnode],t2[maxnode],c[maxnode],n,m,height[maxnode]={0},rank[maxnode]={0}; char s[maxnode]; bool cmp(int *y,int a,int b,int k) { int a2,b2; a2= a+k>=n ? -1 : y[a+k]; b2= b+k>=n ? -1 : y[b+k]; a=y[a];b=y[b]; return a==b&&a2==b2; } void build() { int *x=t1,*y=t2,i,k,p; for (i=0;i<=m;++i) c[i]=0; for (i=0;i<n;++i) ++c[x[i]=(s[i]-'0')]; for (i=1;i<=m;++i) c[i]+=c[i-1]; for (i=n-1;i>=0;--i) sa[--c[x[i]]]=i; for (k=1;k<=n;k<<=1) { p=0; for (i=n-k;i<n;++i) y[p++]=i; for (i=0;i<n;++i) if (sa[i]>=k) y[p++]=sa[i]-k; for (i=0;i<=m;++i) c[i]=0; for (i=0;i<n;++i) ++c[x[y[i]]]; for (i=1;i<=m;++i) c[i]+=c[i-1]; for (i=n-1;i>=0;--i) sa[--c[x[y[i]]]]=y[i]; swap(x,y);m=1;x[sa[0]]=0; for (i=1;i<n;++i) x[sa[i]]= cmp(y,sa[i],sa[i-1],k) ? m-1 : m++; if (m>=n) break; } } void pre() { int k=0,i,j; for (i=0;i<n;++i) rank[sa[i]]=i; for (i=0;i<n;++i) { if (!rank[i]) continue; if (k) --k;j=sa[rank[i]-1]; while(s[i+k]==s[j+k]) ++k; height[rank[i]]=k; } } int main() { int i,j,k; scanf("%d",&n);m=1; for (i=0;i<n;++i) { while(scanf("%c",&s[i])==1) if (s[i]>='0'&&s[i]<='1') break; } build();pre(); for (i=0;i<n;++i) { for (j=height[i]+1;j+sa[i]<=n;++j) { for (k=i+1;k<n&&height[k]>=j;++k); if (k-i>1) printf("%d\n",k-i); } } }
bzoj3879 SvT
题目大意:给定一个字符串,多组询问,每次问某些后缀两两之间lcp的长度和。
思路:对字符串建后缀数组,对于每组询问,按rank排序后,求出相邻两个的lcp,对这个排序之后,类似差异的做法,用这个最小值更新lcp也是这个的答案。
注意:st表预处理的时候,i后面2^j个的时候,要判断i+2^j是否小于n,否则会re。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 500005 #define M 3000005 #define up 20 #define inf 2100000000 #define LL long long #define p 23333333333333333LL using namespace std; char s[N]; struct tree{ int mx,mn,del; void init(){mx=-inf;mn=inf;del=1;} }tr[N<<2]; struct use{ int mn,po; bool operator<(const use&x)const{return mn<x.mn;} }ai[M]; int x1[N],x2[N],n,m,sa[N],rank[N],height[N]={0},ci[N],cc[N],lo[N],mn[N][up]; int cmp(int *y,int a,int b,int k){ int aa,bb; aa=(a+k>=n ? -1 : y[a+k]); bb=(b+k>=n ? -1 : y[b+k]); a=y[a];b=y[b]; return a==b&&aa==bb;} int mcm(int x,int y){return rank[x]<rank[y];} void getsa(){ int i,k,pp;int *x=x1;int *y=x2; for (i=0;i<m;++i) cc[i]=0; for (i=0;i<n;++i) ++cc[x[i]=s[i]-'a']; for (i=1;i<m;++i) cc[i]+=cc[i-1]; for (i=n-1;i>=0;--i) sa[--cc[x[i]]]=i; for (k=1;k<=n;k<<=1){ for (pp=0,i=n-k;i<n;++i) y[pp++]=i; for (i=0;i<n;++i) if (sa[i]>=k) y[pp++]=sa[i]-k; for (i=0;i<m;++i) cc[i]=0; for (i=0;i<n;++i) ++cc[x[y[i]]]; for (i=1;i<m;++i) cc[i]+=cc[i-1]; for (i=n-1;i>=0;--i) sa[--cc[x[y[i]]]]=y[i]; swap(x,y);m=1;x[sa[0]]=0; for (i=1;i<n;++i) x[sa[i]]=(cmp(y,sa[i],sa[i-1],k) ? m-1 : m++); if (m>=n) break; } } void pre(){ int i,j,k=0; for (i=0;i<n;++i) rank[sa[i]]=i; for (i=0;i<n;++i){ if (!rank[i]) continue; if (k) --k; j=sa[rank[i]-1]; while(s[j+k]==s[i+k]) ++k; height[rank[i]]=k; }memset(mn,127/3,sizeof(mn)); for (i=0;i<n;++i) mn[i][0]=height[i]; for (i=1;(1<<i)<=n;++i){ for (j=0;j+(1<<(i-1))<n;++j) mn[j][i]=min(mn[j][i-1],mn[j+(1<<(i-1))][i-1]); } for (k=0,i=1;i<=n;++i){ if ((1<<(k+1))<=i) ++k; lo[i]=k; } } void pushdown(int i){ tr[i<<1].mx=tr[i<<1|1].mx=-inf; tr[i<<1].mn=tr[i<<1|1].mn=inf; tr[i<<1].del^=1;tr[i<<1|1].del^=1; tr[i].del=0;} tree updata(tree x,tree y){ tree c;c.del=0; c.mx=max(x.mx,y.mx); c.mn=min(x.mn,y.mn); return c;} void ins(int i,int l,int r,int x){ if (l==r){tr[i]=(tree){l,l,0};return;} int mid=(l+r)>>1; if (tr[i].del) pushdown(i); if (x<=mid) ins(i<<1,l,mid,x); else ins(i<<1|1,mid+1,r,x); tr[i]=updata(tr[i<<1],tr[i<<1|1]);} int amx(int i,int l,int r,int ll,int rr){ if (ll<=l&&r<=rr) return tr[i].mx; int mid=(l+r)>>1;int mm=-inf; if (tr[i].del) pushdown(i); if (ll<=mid) mm=max(mm,amx(i<<1,l,mid,ll,rr)); if (rr>mid) mm=max(mm,amx(i<<1|1,mid+1,r,ll,rr)); return mm;} int amn(int i,int l,int r,int ll,int rr){ if (ll<=l&&r<=rr) return tr[i].mn; int mid=(l+r)>>1;int mm=inf; if (tr[i].del) pushdown(i); if (ll<=mid) mm=min(mm,amn(i<<1,l,mid,ll,rr)); if (rr>mid) mm=min(mm,amn(i<<1|1,mid+1,r,ll,rr)); return mm;} int getmn(int l,int r){ int x=lo[r-l];++l; return min(mn[l][x],mn[r-(1<<x)+1][x]);} int main(){ int i,j,q,t,l,r;LL ans;scanf("%d%d",&n,&q); scanf("%s",s);m=26; getsa();pre(); while(q--){ scanf("%d",&t); for (i=1;i<=t;++i){scanf("%d",&ci[i]);--ci[i];} sort(ci+1,ci+t+1,mcm);t=unique(ci+1,ci+t+1)-ci-1; for (i=2;i<=t;++i) ai[i]=(use){getmn(rank[ci[i-1]],rank[ci[i]]),i}; sort(ai+2,ai+t+1);ans=0LL; tr[1].init(); ins(1,1,t+1,1);ins(1,1,t+1,t+1); for (i=2;i<=t;++i){ j=ai[i].po; l=amx(1,1,t+1,1,j-1); r=amn(1,1,t+1,j+1,t+1); ans=(ans+(LL)ai[i].mn*(LL)(j-l)*(LL)(r-j)%p)%p; ins(1,1,t+1,j); }printf("%I64d\n",ans); } }
bzoj4453 cys就是要拿英魂!(!!!)
题目大意:给定一个字符串,每次询问一个区间内的最大子串。
思路:这个子串肯定是这个区间的一个后缀,所以考虑求一个区间最大后缀。如果按照左端点从大到小扫的话,每个点处取得最大值的位置是单调不减的,所以可以用一个栈维护这样的决策序列,先弹掉整个区间都不优的,之后从那个可能优的区间里二分,就是这个点能影响到的范围,比较两个后缀大小就是比较lcp之后的那个字母(如果后缀的lcp超过短串的长度,就是长串更长),所以可以用后缀数组预处理。
注意:后缀数组求lcp时下标是rank。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define up 20 #define inf 2100000000 using namespace std; struct use{ int x,l,r; bool operator<(const use&x)const{return l>x.l;} }ai[N],zh[N]; char ss[N]; int zt=0,lo[N],st[N][up],sa[N],height[N],rank[N],x1[N],x2[N],cc[N],tr[N<<2],del[N<<2]; int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} int cmp(int n,int *y,int a,int b,int k){ int aa,bb; aa=(a+k>=n ? -1 : y[a+k]); bb=(b+k>=n ? -1 : y[b+k]); a=y[a];b=y[b]; return (a==b&&aa==bb);} void pre(int n,int m){ int i,j,k,p;int *x=x1;int *y=x2; for (i=0;i<m;++i) cc[i]=0; for (i=0;i<n;++i) ++cc[x[i]=ss[i]]; for (i=1;i<m;++i) cc[i]+=cc[i-1]; for (i=n-1;i>=0;--i) sa[--cc[x[i]]]=i; for (k=1;k<=n;k<<=1){ for (p=0,i=n-k;i<n;++i) y[p++]=i; for (i=0;i<n;++i) if (sa[i]>=k) y[p++]=sa[i]-k; for (i=0;i<m;++i) cc[i]=0; for (i=0;i<n;++i) ++cc[x[y[i]]]; for (i=1;i<m;++i) cc[i]+=cc[i-1]; for (i=n-1;i>=0;--i) sa[--cc[x[y[i]]]]=y[i]; swap(x,y);m=1;x[sa[0]]=0; for (i=1;i<n;++i) x[sa[i]]=(cmp(n,y,sa[i],sa[i-1],k) ? m-1 : m++); if (m>=n) break; }for (i=0;i<n;++i) rank[sa[i]]=i; height[rank[0]]=k=0; for (i=0;i<n;++i){ if (!rank[i]) continue; if (k) --k; j=sa[rank[i]-1]; while(ss[i+k]==ss[j+k]) ++k; height[rank[i]]=k; }for (i=0;i<n;++i) st[i][0]=height[i]; for (j=1;(1<<j)<=n;++j) for (i=0;i+(1<<(j-1))<n;++i) st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]); for (j=0,i=1;i<=n;++i){ if ((1<<(j+1))<=i) ++j; lo[i]=j; } } int lcp(int l,int r){ l=rank[l];r=rank[r]; if (l>r) swap(l,r); int d=lo[r-l];++l; return min(st[l][d],st[r-(1<<d)+1][d]);} bool mcm(int x,int y,int r){ int lc=lcp(x,y); if (y+lc>r) return true; return ss[x+lc]>ss[y+lc];} void pushdown(int i){ del[i<<1]=del[i<<1|1]=del[i]; tr[i<<1]=tr[i<<1|1]=del[i]; del[i]=-1;} void tch(int i,int l,int r,int ll,int rr,int x){ if (ll<=l&&r<=rr){tr[i]=del[i]=x;return;} int mid=(l+r)>>1; if (del[i]>=0) pushdown(i); if (ll<=mid) tch(i<<1,l,mid,ll,rr,x); if (rr>mid) tch(i<<1|1,mid+1,r,ll,rr,x);} int geta(int i,int l,int r,int x){ if (l==r) return tr[i]; int mid=(l+r)>>1; if (del[i]>=0) pushdown(i); if (x<=mid) return geta(i<<1,l,mid,x); else return geta(i<<1|1,mid+1,r,x);} int gete(int x,use y){ int l,r,mid,ans=y.l-1; l=y.l;r=y.r; while(l<=r){ mid=(l+r)>>1; if (mcm(x,y.x,mid)){l=mid+1;ans=mid;} else r=mid-1; }return ans;} void getz(int l,int x){ while(zt){ if (mcm(x,zh[zt].x,zh[zt].r)) --zt; else break; }if (!zt){ zh[++zt]=(use){x,x,zh[0].r-1}; tch(1,0,l-1,x,l-1,x); }else{ int r=gete(x,zh[zt]); zh[zt].l=r+1; zh[++zt]=(use){x,x,r}; tch(1,0,l-1,x,r,x); } } int main(){ int i,j,l,n,m=0; scanf("%s",ss);l=strlen(ss); for (i=0;i<l;++i) m=max(m,ss[i]+1); for (pre(l,m),n=in(),i=1;i<=n;++i) ai[i]=(use){i,in()-1,in()-1}; sort(ai+1,ai+n+1); zh[0]=(use){0,0,l}; memset(del,-60,sizeof(del)); for (j=1,i=l-1;i>=0;--i){ getz(l,i); for (;j<=n&&ai[j].l==i;++j) x1[ai[j].x]=geta(1,0,l-1,ai[j].r); }for (i=1;i<=n;++i) printf("%d\n",x1[i]+1); }
bzoj4556 字符串
题目大意:给定一个字符串s,m组询问:s[a...b]的子串和s[c...d]的最长公共前缀。
思路:用后缀数组求出height之后,二分答案,每次找height满足要求的区间内找有没有在[a,b-mid+1]范围内的,用主席树(第一层是rank,第二层是位置)查询。
注意:1)不要读错题;
2)如果找rank的前驱后继,可能会出现公共前缀很长,但是被b这个位置截掉的情况。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define up 20 #define M 2000005 using namespace std; int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} struct use{int l,r,sm;}tr[M]; int n,m,sa[N],rank[N],height[N],ci[N],x1[N],x2[N],lo[N],di[up][N],rt[N]={0},tt=0; char ss[N]; bool cmp(int *y,int a,int b,int k){ int aa,bb; aa=(a+k>=n ? -1 : y[a+k]); bb=(b+k>=n ? -1 : y[b+k]); a=y[a];b=y[b]; return a==b&&aa==bb;} void pre(){ int i,j,k,p;m=26;int *x=x1;int *y=x2; for (i=0;i<m;++i) ci[i]=0; for (i=0;i<n;++i) ++ci[x[i]=(ss[i]-'a')]; for (i=1;i<m;++i) ci[i]+=ci[i-1]; for (i=n-1;i>=0;--i) sa[--ci[x[i]]]=i; for (k=1;k<=n;k<<=1){ for (p=0,i=n-k;i<n;++i) y[p++]=i; for (i=0;i<n;++i) if (sa[i]>=k) y[p++]=sa[i]-k; for (i=0;i<m;++i) ci[i]=0; for (i=0;i<n;++i) ++ci[x[y[i]]]; for (i=1;i<m;++i) ci[i]+=ci[i-1]; for (i=n-1;i>=0;--i) sa[--ci[x[y[i]]]]=y[i]; swap(x,y);m=1;x[sa[0]]=0; for (i=1;i<n;++i) x[sa[i]]=(cmp(y,sa[i],sa[i-1],k) ? m-1 : m++); if (m>=n) break; }for (i=0;i<n;++i) rank[sa[i]]=i; for (k=i=0;i<n;++i){ if (!rank[i]) continue; if (k) --k;j=sa[rank[i]-1]; while(ss[i+k]==ss[j+k]) ++k; height[rank[i]]=k; }for (i=0;i<n;++i) di[0][i]=height[i]; for (i=1;(1<<i)<=n;++i) for (j=0;j+(1<<(i-1))<n;++j) di[i][j]=min(di[i-1][j],di[i-1][j+(1<<(i-1))]); for (k=0,i=1;i<=n;++i){ if ((1<<(k+1))<=i) ++k; lo[i]=k; } } int amn(int x,int y){ if (x==y) return (n-sa[x]); if (x>y) swap(x,y); int k=lo[y-x];++x; return min(di[k][x],di[k][y-(1<<k)+1]); } void ins(int &i,int la,int l,int r,int x){ tr[i=++tt]=tr[la];++tr[i].sm; if (l==r) return; int mid=(l+r)>>1; if (x<=mid) ins(tr[i].l,tr[la].l,l,mid,x); else ins(tr[i].r,tr[la].r,mid+1,r,x); } int ask(int i,int j,int l,int r,int ll,int rr){ if (!(tr[j].sm-tr[i].sm)) return 0; if (ll<=l&&r<=rr) return tr[j].sm-tr[i].sm; int sm=0,mid=(l+r)>>1; if (ll<=mid) sm+=ask(tr[i].l,tr[j].l,l,mid,ll,rr); if (rr>mid) sm+=ask(tr[i].r,tr[j].r,mid+1,r,ll,rr); return sm;} int getl(int c,int x){ int l,r,mid,ans; l=1;r=rank[c-1];ans=r+1; while(l<=r){ mid=(l+r)>>1; if (amn(mid-1,rank[c-1])>=x){r=mid-1;ans=mid;} else l=mid+1; }return ans-1; } int getr(int c,int x){ int l,r,mid,ans; l=rank[c-1]+1;r=n-1;ans=l-1; while(l<=r){ mid=(l+r)>>1; if (amn(rank[c-1],mid)>=x){l=mid+1;ans=mid;} else r=mid-1; }return ans; } int query(int a,int b,int c,int d){ int l,r,le,re,mid,mx=0; l=0;r=min(d-c+1,b-a+1); while(l<=r){ mid=(l+r)>>1; le=getl(c,mid); re=getr(c,mid); if (ask(rt[le],rt[re+1],1,n,a,b-mid+1)){l=mid+1;mx=mid;} else r=mid-1; }return mx; } int main(){ int q,i,a,b,c,d; n=in();q=in(); scanf("%s",ss);pre(); for (i=1;i<=n;++i) ins(rt[i],rt[i-1],1,n,sa[i-1]+1); for (i=1;i<=q;++i){ a=in();b=in();c=in();d=in(); printf("%d\n",query(a,b,c,d)); } }
bzoj2119 股市的预测(!!!)
题目大意:求隔过长度为m-1的段后两边(不一定是全部)的升降幅度一样的子串个数。
思路:差分之后就是求ABA的形式,其中B的长度为m的个数。枚举A的长度i,每i个点的位置为j,j和i+m+j比较一下求出向前向后最多能扩展的长度x,如果长度>=i就可以累加答案x-i+1,注意这里x是在这个j的范围内,不能超过j-i+1和j+i,否则会重复统计。
orz vaorz vaorz va
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define up 17 #define LL long long using namespace std; int in(){ char ch=getchar();int x=0,f=1; while((ch<'0'||ch>'9')&&ch!='-') ch=getchar(); if (ch=='-'){f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x*f;} int cz=0,ai[N],x1[N],x2[N],sa[N],rank[N],height[N]={0},cc[N],st[up][N],lo[N]; LL ci[N]; int cmp(int n,int *y,int a,int b,int k){ int aa,bb; aa=(a+k>=n ? -1 : y[a+k]); bb=(b+k>=n ? -1 : y[b+k]); a=y[a];b=y[b]; return a==b&&aa==bb;} void pre(int n,int m){ int i,j,p,k;int *x=x1;int *y=x2; for (i=0;i<m;++i) cc[i]=0; for (i=0;i<n;++i) ++cc[x[i]=ai[i]]; for (i=1;i<m;++i) cc[i]+=cc[i-1]; for (i=n-1;i>=0;--i) sa[--cc[x[i]]]=i; for (k=1;k<=n;k<<=1){ for (p=0,i=n-k;i<n;++i) y[p++]=i; for (i=0;i<n;++i) if (sa[i]>=k) y[p++]=sa[i]-k; for (i=0;i<m;++i) cc[i]=0; for (i=0;i<n;++i) ++cc[x[y[i]]]; for (i=1;i<m;++i) cc[i]+=cc[i-1]; for (i=n-1;i>=0;--i) sa[--cc[x[y[i]]]]=y[i]; swap(x,y);m=1;x[sa[0]]=0; for (i=1;i<n;++i) x[sa[i]]=(cmp(n,y,sa[i],sa[i-1],k) ? m-1 : m++); if (m>=n) break; }for (i=0;i<n;++i) rank[sa[i]]=i; for (k=i=0;i<n;++i){ if (!rank[i]) continue; if (k) --k;j=sa[rank[i]-1]; for (;ai[i+k]==ai[j+k];++k); height[rank[i]]=k; }for (i=0;i<n;++i) st[0][i]=height[i]; for (i=1;(1<<i)<=n;++i) for (j=0;j+(1<<(i-1))<n;++j) st[i][j]=min(st[i-1][j],st[i-1][j+(1<<(i-1))]); for (k=0,i=1;i<=n;++i){ if ((1<<(k+1))<=i) ++k; lo[i]=k; } } int amn(int x,int y){ if (x>y) swap(x,y); int k=lo[y-x];++x; return min(st[k][x],st[k][y-(1<<k)+1]); } int main(){ int i,j,a,b,n,m;LL ans=0LL;n=in();m=in(); for (i=1;i<=n;++i){ ai[i]=in(); if (i>1) ci[++cz]=(LL)ai[i]-ai[i-1]; }sort(ci+1,ci+cz+1); cz=unique(ci+1,ci+cz+1)-ci-1; for (--n,i=1;i<=n;++i) ai[i-1]=upper_bound(ci+1,ci+cz+1,(LL)ai[i+1]-ai[i])-ci-2; ai[n]=cz;++cz; for (i=1;i<=n;++i) ai[n*2+1-i]=ai[i-1]; pre(n*2+1,cz); for (i=1;i+i+m<=n;++i) for (a=0,j=0;j+i+m<n;j+=i){ b=min(i,amn(rank[j],rank[j+i+m])); if (a+b>=i) ans+=(LL)(a+b-i+1); a=min(i-1,amn(rank[n*2-(i+j-1)],rank[n*2-(i+i+j+m-1)])); } printf("%I64d\n",ans); }
bzoj4566 找相同字符
题目大意:求a和b串的公共子串个数(位置不同的算不同的)。
思路:中间加一个不再字符集的字母接起来。和差异一样,只是统计答案的时候是左右边分别属于a、b的个数的乘积。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 400005 #define LL long long using namespace std; struct use{int mn,po;}tr[N<<2],ci; int n=0,n1,sa[N],rank[N],height[N],x1[N],x2[N],cc[N],seg[N<<2]; LL ans=0LL; char ss[N]; void in(){ char ch=getchar(); while(ch<'a'||ch>'z') ch=getchar(); while(ch>='a'&&ch<='z'){ ss[n++]=ch;ch=getchar(); } } int cmp(int *y,int a,int b,int k){ int aa,bb; aa=(a+k>=n ? -1 : y[a+k]); bb=(b+k>=n ? -1 : y[b+k]); a=y[a];b=y[b]; return (a==b&&aa==bb); } void pre(){ int i,j,k,p,m;int *x=x1;int *y=x2; for (m=27,i=0;i<m;++i) cc[i]=0; for (i=0;i<n;++i) ++cc[x[i]=ss[i]-'a']; for (i=1;i<m;++i) cc[i]+=cc[i-1]; for (i=n-1;i>=0;--i) sa[--cc[x[i]]]=i; for (k=1;k<=n;k<<=1){ for (p=0,i=n-k;i<n;++i) y[p++]=i; for (i=0;i<n;++i) if (sa[i]>=k) y[p++]=sa[i]-k; for (i=0;i<m;++i) cc[i]=0; for (i=0;i<n;++i) ++cc[x[y[i]]]; for (i=1;i<m;++i) cc[i]+=cc[i-1]; for (i=n-1;i>=0;--i) sa[--cc[x[y[i]]]]=y[i]; swap(x,y);m=1;x[sa[0]]=0; for (i=1;i<n;++i) x[sa[i]]=(cmp(y,sa[i],sa[i-1],k) ? m-1 : m++); if (m>=n) break; }for (i=0;i<n;++i) rank[sa[i]]=i; for (k=0,i=0;i<n;++i){ if (!rank[i]) continue; if (k) --k;j=sa[rank[i]-1]; for (;ss[i+k]==ss[j+k];++k); height[rank[i]]=k; } } use updata(use x,use y){ use c; if (x.mn<=y.mn) c=x; else c=y; return c;} void build(int i,int l,int r){ if (l==r){tr[i]=(use){height[l],l};return;} int mid=(l+r)>>1; build(i<<1,l,mid);build(i<<1|1,mid+1,r); tr[i]=updata(tr[i<<1],tr[i<<1|1]); } void buils(int i,int l,int r){ if (l==r){seg[i]=(sa[l]<n1);return;} int mid=(l+r)>>1; buils(i<<1,l,mid);buils(i<<1|1,mid+1,r); seg[i]=seg[i<<1]+seg[i<<1|1]; } void amn(int i,int l,int r,int ll,int rr){ if (ll<=l&&r<=rr){ if (l==ll) ci=tr[i]; else ci=updata(ci,tr[i]); return; }int mid=(l+r)>>1; if (ll<=mid) amn(i<<1,l,mid,ll,rr); if (rr>mid) amn(i<<1|1,mid+1,r,ll,rr); } int ask(int i,int l,int r,int ll,int rr){ if (ll<=l&&r<=rr) return seg[i]; int sm=0,mid=(l+r)>>1; if (ll<=mid) sm+=ask(i<<1,l,mid,ll,rr); if (rr>mid) sm+=ask(i<<1|1,mid+1,r,ll,rr); return sm; } void work(int l,int r){ if (l>=r) return; amn(1,1,n-1,l+1,r); int s1,s2; s1=ask(1,0,n-1,l,ci.po-1); s2=ask(1,0,n-1,ci.po,r); ans+=(LL)ci.mn*((LL)s1*(LL)(r-ci.po+1-s2)+(LL)(ci.po-l-s1)*(LL)s2); s1=ci.po;work(l,s1-1);work(s1,r); } int main(){ in();n1=n;ss[n++]='z'+1; in();pre(); build(1,1,n-1);buils(1,0,n-1); work(0,n-1); printf("%I64d\n",ans); }
后缀平衡树
bzoj2555
题目大意:维护一个字符串,支持:(1)在末尾加一段字符;(2)查询一个字符串出现的次数。
思路:维护前缀,用平衡树动态加入,比较的时候可以用二分+hash求出lcp,然后比较大小。查询x的次数的时候相当于<xS - <x的(S是一个比字符集大的字符)。
注意:卡常技巧:(1)用unsigned int比unsigned long long快;(2)二分时定一个参数,如果参数长度的不同就把上界设为参数(参数取得25)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 3000005 #define p 569LL #define up 26 #define UL unsigned int #define ji 25 using namespace std; UL ha[N],mi[N],ha1[N]; int le=0,l1,qt=0; char ss[N],s1[N]; inline UL geth(int k,int l,int r){ if (k) return (ha1[r]-(l-1>=0 ? ha1[l-1] : 0LL))*mi[N-r-1]; else return (ha[r]-(l-1>=0 ? ha[l-1] : 0LL))*mi[N-r-1];} struct node{ node *ch[2]; int po,r,sz; int cmp(int k){ char *s=(k ? s1 : ss); int l,r,mid,lc,len=(k ? l1 : le); lc=l=0; if (min(po,len)<=ji||(geth(0,po-ji,po-1)==geth(k,len-ji,len-1))) r=min(po,len); else r=ji; while(l<=r){ mid=(l+r)>>1; if (geth(0,po-mid,po-1)==geth(k,len-mid,len-1)){lc=mid;l=mid+1;} else r=mid-1; }if (po==len&&lc==len) return -1; if (lc==po) return 1; if (lc==len) return 0; return (ss[po-lc-1]<s[len-lc-1]); } inline void updata(){ sz=1; if (ch[0]!=NULL) sz+=ch[0]->sz; if (ch[1]!=NULL) sz+=ch[1]->sz; } }*rt,que[N*3]; inline void in(){ l1=0;char ch=getchar(); while(ch<'A'||ch>'Z') ch=getchar(); while(ch>='A'&&ch<='Z'){ s1[l1++]=ch;ch=getchar(); } } inline void decode(int mask){ for (int i=0;i<l1;++i){ mask=(mask*131+i)%l1; swap(s1[i],s1[mask]); } } inline void rotate(node* &o,int d){ node *k=o->ch[d^1];o->ch[d^1]=k->ch[d];k->ch[d]=o; o->updata();k->updata();o=k;} void ins(node* &o){ if (o==NULL){ o=&que[qt++]; o->po=le;o->r=rand(); o->ch[0]=o->ch[1]=NULL; o->sz=1; }else{ int d=o->cmp(0);ins(o->ch[d]); if (o->ch[d]->r > o->r) rotate(o,d^1); o->updata(); } } int rank(node *o){ if (o==NULL) return 0; int d=o->cmp(1); if (d==-1) return (o->ch[0]==NULL ? 0 : o->ch[0]->sz); if (!d) return rank(o->ch[0]); else return (o->ch[0]==NULL ? 0 : o->ch[0]->sz)+1+rank(o->ch[1]); } int main(){ int i,j,m,ans,mask=0; scanf("%d",&m);in(); for (mi[0]=1LL,i=1;i<N;++i) mi[i]=mi[i-1]*p; for (i=0;i<l1;++i){ ha[le]=(le ? ha[le-1] : 0LL)+(UL)(s1[i]-'A')*mi[le]; ss[le++]=s1[i];ins(rt); }while(m--){ in(); if (s1[0]=='A'){ in();decode(mask); for (i=0;i<l1;++i){ ha[le]=ha[le-1]+(UL)(s1[i]-'A')*mi[le]; ss[le++]=s1[i]; ins(rt); } }else{ in();decode(mask); for (i=0;i<l1;++i) ha1[i]=(i ? ha1[i-1] : 0LL)+(UL)(s1[i]-'A')*mi[i]; ans=-rank(rt); for (i=l1;i;--i) s1[i]=s1[i-1]; s1[0]=up+'A';++l1; for (i=0;i<l1;++i) ha1[i]=(i ? ha1[i-1] : 0LL)+(UL)(s1[i]-'A')*mi[i]; ans+=rank(rt); mask^=ans;printf("%d\n",ans); } } }