在夏令营期间学习了作为一个字符串处理神器的后缀数组。
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); }