都是论文里的例题~
①最长公共前缀
给一个字符串,询问某两个后缀的最长公共前缀。
对于suffix(l)和suffix(r),如果l=r的话要特判一下。否则如果rank[l]<rank[r],那么ans=min(height[rank[l]+1]…height[rank[r]])。
②可重叠最长重复子串
给定一个字符串,求最长的重复两次的子串,这两个子串可以重叠。
最长重复子串,等价于求两个后缀的最长公共前缀的最大值。因为任意两个后缀的最长公共前缀都是height数组里某一段的最小值,那么这个值一定不大于height数组里的最大值。所以我们只要求height数组最大值即可,这个值显然是一个可行解。
③不可重叠最长重复子串(poj1743)
给定一个字符串,求最长的重复两次的子串,这两个子串不可以重叠。
首先我们来二分答案,假设二分到k,那么就是要判断有没有两个长度为k的相等的不重叠子串。我们把排序后的后缀分成若干组,其中每组的后缀之间的height值都>=k。
那么最长公共前缀不小于k的两个后缀一定在同一组,所以如果在一组中出现两个sa绝对值差>=k就是可行的。否则不可行。
//不可重叠最长重复子串 poj1743 //O(nlogn) #include <iostream> #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <vector> #include <limits> #include <set> #include <map> using namespace std; #define SZ 666666 int n,sa[SZ],t[SZ],rank[SZ],qzh[SZ],tmpsa[SZ],tmpr[SZ],h[SZ],s[SZ]; bool same(int a,int b,int p) {return t[a]==t[b]&&t[a+p]==t[b+p];} void getsa(int n,int m=500) { for(int i=0;i<n;i++) rank[i]=s[i], ++qzh[rank[i]]; for(int i=1;i<m;i++) qzh[i]+=qzh[i-1]; for(int i=n-1;i>=0;i--) sa[--qzh[rank[i]]]=i; for(int j=1;j<=n;j<<=1) { int cur=-1; for(int i=n-j;i<n;i++) tmpsa[++cur]=i; for(int i=0;i<n;i++) if(sa[i]>=j) tmpsa[++cur]=sa[i]-j; for(int i=0;i<n;i++) tmpr[i]=rank[tmpsa[i]]; for(int i=0;i<m;i++) qzh[i]=0; for(int i=0;i<n;i++) ++qzh[tmpr[i]]; for(int i=1;i<m;i++) qzh[i]+=qzh[i-1]; for(int i=n-1;i>=0;i--) t[i]=rank[i], sa[--qzh[tmpr[i]]]=tmpsa[i]; m=0; for(int i=0;i<n;i++) rank[sa[i]]=(i>0&&same(sa[i],sa[i-1],j))?m:++m; ++m; } for(int i=0;i<n;i++) rank[sa[i]]=i; } void geth(int n) { int p=0; for(int i=0;i<n;i++) { if(p) --p; int ls=sa[rank[i]-1]; while(s[ls+p]==s[i+p]) p++; h[rank[i]]=p; } } bool ok(int n,int k) { int maxn=sa[1],minn=sa[1]; for(int i=2;i<n;i++) { if(h[i]>=k) { int cur=sa[i]; maxn=max(maxn,cur); minn=min(minn,cur); if(maxn-minn>=k) return 1; continue; } maxn=sa[i]; minn=sa[i]; } return 0; } void fff() { memset(s,0,sizeof(s)); memset(qzh,0,sizeof(qzh)); memset(rank,0,sizeof(rank)); memset(sa,0,sizeof(sa)); memset(t,0,sizeof(t)); int lst; scanf("%d",&lst); --n; for(int i=0;i<n;i++) { int y; scanf("%d",&y); s[i]=y-lst+200; lst=y; } if(n<10) {puts("0"); return;} //wtf s[n]=0; getsa(n+1); geth(n+1); int l=0,r=n; while(l<r) { int mid=(l+r+1)>>1; if(ok(n+1,mid)) l=mid; else r=mid-1; } ++l; if(l<5) l=0; printf("%d ",l); } int main() { while(scanf("%d",&n),n) fff(); }
④可重叠的k次最长重复子串(poj3261)
给定一个字符串,求最长的重复k次的子串,这两个子串可以重叠。
二分答案,把后缀分组,判断有没有一组长度大于等于k。
//可重叠的k次最长重复子串 poj3261 //O(nlogn) #include <iostream> #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <vector> #include <limits> #include <set> #include <map> using namespace std; #define SZ 666666 int n,k,sa[SZ],t[SZ],rank[SZ],qzh[SZ],tmpsa[SZ],tmpr[SZ],h[SZ],s[SZ]; bool same(int a,int b,int p) {return t[a]==t[b]&&t[a+p]==t[b+p];} void getsa(int n,int m=500) { for(int i=0;i<n;i++) rank[i]=s[i], ++qzh[rank[i]]; for(int i=1;i<m;i++) qzh[i]+=qzh[i-1]; for(int i=n-1;i>=0;i--) sa[--qzh[rank[i]]]=i; for(int j=1;j<=n;j<<=1) { int cur=-1; for(int i=n-j;i<n;i++) tmpsa[++cur]=i; for(int i=0;i<n;i++) if(sa[i]>=j) tmpsa[++cur]=sa[i]-j; for(int i=0;i<n;i++) tmpr[i]=rank[tmpsa[i]]; for(int i=0;i<m;i++) qzh[i]=0; for(int i=0;i<n;i++) ++qzh[tmpr[i]]; for(int i=1;i<m;i++) qzh[i]+=qzh[i-1]; for(int i=n-1;i>=0;i--) t[i]=rank[i], sa[--qzh[tmpr[i]]]=tmpsa[i]; m=0; for(int i=0;i<n;i++) rank[sa[i]]=(i>0&&same(sa[i],sa[i-1],j))?m:++m; ++m; } for(int i=0;i<n;i++) rank[sa[i]]=i; } void geth(int n) { int p=0; for(int i=0;i<n;i++) { if(p) --p; int ls=sa[rank[i]-1]; while(s[ls+p]==s[i+p]) p++; h[rank[i]]=p; } } bool ok(int n,int g) { int rp=1; for(int i=2;i<n;i++) { if(h[i]>=g) ++rp; else rp=1; if(rp>=k) return 1; } return rp>=k; } int main() { scanf("%d%d",&n,&k); for(int i=0;i<n;i++) scanf("%d",s+i); s[n]=0; getsa(n+1); geth(n+1); int l=0,r=n; while(l<r) { int mid=(l+r+1)>>1; if(ok(n+1,mid)) l=mid; else r=mid-1; } printf("%d ",l); }
每个子串一定是某个后缀的前缀,那么答案就是所有后缀之间的不同前缀个数。如果所有的后缀按照suffix(sa[1]),suffix(sa[2]),suffix(sa[3]),……,suffix(sa[n])的顺序计算,不难发现,对于每一次新加进来的后缀suffix(sa[k]),它将产生n-sa[k]+1个新的前缀。但是其中有height[k]个是和前面的字符串的前缀是相同的。所以suffix(sa[k])将贡献n-sa[k]+1-height[k]个不同的子串。因为n-sa[k]+1的和等于(n+1)*n-(n+1)*n/2=(n+1)*n/2,所以我们可以直接用(n+1)*n/2代替n-sa[k]+1的和。
//不同子串个数 spoj694/spoj705 //O(nlogn) #include <iostream> #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <vector> #include <limits> #include <set> #include <map> using namespace std; #define SZ 666666 int n,k,sa[SZ],t[SZ],rank[SZ],qzh[SZ],tmpsa[SZ],tmpr[SZ],h[SZ]; char s[SZ]; bool same(int a,int b,int p) {return t[a]==t[b]&&t[a+p]==t[b+p];} void getsa(int n,int m=500) { for(int i=0;i<n;i++) rank[i]=s[i], ++qzh[rank[i]]; for(int i=1;i<m;i++) qzh[i]+=qzh[i-1]; for(int i=n-1;i>=0;i--) sa[--qzh[rank[i]]]=i; for(int j=1;j<=n;j<<=1) { int cur=-1; for(int i=n-j;i<n;i++) tmpsa[++cur]=i; for(int i=0;i<n;i++) if(sa[i]>=j) tmpsa[++cur]=sa[i]-j; for(int i=0;i<n;i++) tmpr[i]=rank[tmpsa[i]]; for(int i=0;i<m;i++) qzh[i]=0; for(int i=0;i<n;i++) ++qzh[tmpr[i]]; for(int i=1;i<m;i++) qzh[i]+=qzh[i-1]; for(int i=n-1;i>=0;i--) t[i]=rank[i], sa[--qzh[tmpr[i]]]=tmpsa[i]; m=0; for(int i=0;i<n;i++) rank[sa[i]]=(i>0&&same(sa[i],sa[i-1],j))?m:++m; ++m; } for(int i=0;i<n;i++) rank[sa[i]]=i; } void geth(int n) { int p=0; for(int i=0;i<n;i++) { if(p) --p; int ls=sa[rank[i]-1]; while(s[ls+p]==s[i+p]) p++; h[rank[i]]=p; } } void fff() { memset(s,0,sizeof(s)); memset(qzh,0,sizeof(qzh)); memset(rank,0,sizeof(rank)); memset(sa,0,sizeof(sa)); memset(t,0,sizeof(t)); scanf("%s",s); n=strlen(s); s[n]=0; getsa(n+1); geth(n+1); long long ans=(long long)n*(n+1)/2; for(int i=2;i<=n;i++) ans-=h[i]; printf("%lld ",ans); } int main() { int T; scanf("%d",&T); while(T--) fff(); }
⑥最长回文子串
先跳过好了…写不出来啊(manacher不知道高到哪里去了
⑦连续重复子串(poj2406)
给定一个字符串,已知这个字符串是由某个字符串重复p次而得到的,求p的最大值。
这题后缀数组的做法好像和哈希差不多…
首先枚举循环节长度p,那么设字符串的长度为l,首先必须得p|l。
然后我们发现我们其实只要比较类似下面这张图中带红线的两段即可!
说得具体一点,即s[0…l-p-1]=s[p…l-1]。
那我们就可以用后缀数组来搞出suffix(0)和suffix(p)的最长公共前缀,判断它是不是>=l-p即可。(hash不知道高到哪里去了
下面这段代码会TLE…等窝学完O(n)的后缀数组建法再过来改(POJ居然会出100W+多组数据强行卡掉带log的算法
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <vector> #include <limits> #include <set> #include <map> using namespace std; #define SZ 2333333 int n,k,sa[SZ],t[SZ],rank[SZ],qzh[SZ],tmpsa[SZ],tmpr[SZ],h[SZ]; char s[SZ]; bool same(int a,int b,int p) {return t[a]==t[b]&&t[a+p]==t[b+p];} void getsa(int n,int m=500) { for(int i=0;i<n;i++) rank[i]=s[i], ++qzh[rank[i]]; for(int i=1;i<m;i++) qzh[i]+=qzh[i-1]; for(int i=n-1;i>=0;i--) sa[--qzh[rank[i]]]=i; for(int j=1;j<=n;j<<=1) { int cur=-1; for(int i=n-j;i<n;i++) tmpsa[++cur]=i; for(int i=0;i<n;i++) if(sa[i]>=j) tmpsa[++cur]=sa[i]-j; for(int i=0;i<n;i++) tmpr[i]=rank[tmpsa[i]]; for(int i=0;i<m;i++) qzh[i]=0; for(int i=0;i<n;i++) ++qzh[tmpr[i]]; for(int i=1;i<m;i++) qzh[i]+=qzh[i-1]; for(int i=n-1;i>=0;i--) t[i]=rank[i], sa[--qzh[tmpr[i]]]=tmpsa[i]; m=0; for(int i=0;i<n;i++) rank[sa[i]]=(i>0&&same(sa[i],sa[i-1],j))?m:++m; ++m; } for(int i=0;i<n;i++) rank[sa[i]]=i; } void geth(int n) { int p=0; for(int i=0;i<n;i++) { if(p) --p; int ls=sa[rank[i]-1]; while(s[ls+p]==s[i+p]) p++; h[rank[i]]=p; } } int seg[SZ],M=1048576; void build(int n) { for(int i=1;i<=n;i++) seg[M+i]=h[i-1]; for(int i=M-1;i>=1;i--) seg[i]=min(seg[i+i],seg[i+i+1]); } int gm(int l,int r) { ++l; ++r; int ans=23333333; for(l+=M-1,r+=M+1;l^r^1;l>>=1,r>>=1) { if(~l&1) ans=min(ans,seg[l^1]); if(r&1) ans=min(ans,seg[r^1]); } return ans; } int getgg(int l,int r) { if(l==r) return 2333333; if(rank[l]>rank[r]) swap(rank[l],rank[r]); return gm(rank[l]+1,rank[r]); } int main() { while(1) { memset(s,0,sizeof(s)); memset(qzh,0,sizeof(qzh)); memset(rank,0,sizeof(rank)); memset(sa,0,sizeof(sa)); memset(t,0,sizeof(t)); int rt=scanf("%s",s); if(rt==-1||s[0]=='.') break; n=strlen(s); s[n]=0; getsa(n+1); geth(n+1); build(n+1); int ans=1; for(int i=1;i<n;i++) { if(n%i) continue; int gg=getgg(0,i); if(gg<n-i) continue; ans=max(ans,n/i); } printf("%d ",ans); } }
⑧最长公共子串(poj2774)
显然求A和B的最长公共子串等价于求A的后缀和B的后缀的最长公共前缀的最大值。
我们把A和B接在一起,中间用一些奇怪的符号,比如$把它隔开。
(盗图一张)
我们发现呢,这个height数组,就是A串中的某些后缀与B串中的某些后缀的公共前缀的长。
然后我们会发现,求A的后缀和B的后缀的最长公共前缀,根据前面的结论,它是等于height数组某一段的最小值的,所以肯定是小于等于height数组最大值的。
但是height数组的最大值不一定是答案,我们要确保每一个height是分属两个字符串的不同后缀才可以用来更新答案。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <vector> #include <limits> #include <set> #include <map> using namespace std; #define SZ 666666 int n,sa[SZ],t[SZ],rank[SZ],qzh[SZ],tmpsa[SZ],tmpr[SZ],h[SZ]; char s[SZ]; bool same(int a,int b,int p) {return t[a]==t[b]&&t[a+p]==t[b+p];} void getsa(int n,int m=233) { for(int i=0;i<n;i++) rank[i]=s[i], ++qzh[rank[i]]; for(int i=1;i<m;i++) qzh[i]+=qzh[i-1]; for(int i=n-1;i>=0;i--) sa[--qzh[rank[i]]]=i; for(int j=1;j<=n;j<<=1) { int cur=-1; for(int i=n-j;i<n;i++) tmpsa[++cur]=i; for(int i=0;i<n;i++) if(sa[i]>=j) tmpsa[++cur]=sa[i]-j; for(int i=0;i<n;i++) tmpr[i]=rank[tmpsa[i]]; for(int i=0;i<m;i++) qzh[i]=0; for(int i=0;i<n;i++) ++qzh[tmpr[i]]; for(int i=1;i<m;i++) qzh[i]+=qzh[i-1]; for(int i=n-1;i>=0;i--) t[i]=rank[i], sa[--qzh[tmpr[i]]]=tmpsa[i]; m=0; for(int i=0;i<n;i++) rank[sa[i]]=(i>0&&same(sa[i],sa[i-1],j))?m:++m; ++m; } for(int i=0;i<n;i++) rank[sa[i]]=i; } void geth(int n) { int p=0; for(int i=0;i<n;i++) { if(p) --p; int ls=sa[rank[i]-1]; while(s[ls+p]==s[i+p]) p++; h[rank[i]]=p; } } char a[SZ],b[SZ],c[3]; int main() { scanf("%s%s",a,b); strcpy(s,a); c[0]='$'; c[1]=0; strcat(s,c); strcat(s,b); int n=strlen(s),an=strlen(a),bn=strlen(b); getsa(n+1); geth(n+1); int ans=0; for(int i=2;i<=n;i++) { int A=sa[i-1],B=sa[i]; if(A>B) swap(A,B); if(A<an&&B>=an+1&&B<n) ans=max(ans,h[i]); } printf("%d ",ans); }