后缀数组suffix array
自用模板:
sa:字典序中排第i位的起使位置在str中第sa[i],sa[1,n]
rk:str第i个位置的后缀在字典序中排第几,rk[1,n]
height:字典序排i和i-1的后缀的最长公共前缀,height[2,n]
#include<bits/stdc++.h> #define maxn 1000050 using namespace std; char s[maxn]; int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn]; int n,m; inline void get_SA() { for(int i=1;i<=n;++i) ++c[x[i]=s[i]]; //c数组是桶 //x[i]是第i个元素的第一关键字 for(int i=2;i<=m;++i) c[i]+=c[i-1]; //做c的前缀和,我们就可以得出每个关键字最多是在第几名 for(int i=n;i>=1;--i) sa[c[x[i]]--]=i; for(int k=1;k<=n;k<<=1) { int num=0; for(int i=n-k+1;i<=n;++i) y[++num]=i; //y[i]表示第二关键字排名为i的数,第一关键字的位置 //第n-k+1到第n位是没有第二关键字的 所以排名在最前面 for(int i=1;i<=n;++i) if(sa[i]>k) y[++num]=sa[i]-k; //排名为i的数 在数组中是否在第k位以后 //如果满足(sa[i]>k) 那么它可以作为别人的第二关键字,就把它的第一关键字的位置添加进y就行了 //所以i枚举的是第二关键字的排名,第二关键字靠前的先入队 for(int i=1;i<=m;++i) c[i]=0; //初始化c桶 for(int i=1;i<=n;++i) ++c[x[i]]; //因为上一次循环已经算出了这次的第一关键字 所以直接加就行了 for(int i=2;i<=m;++i) c[i]+=c[i-1]; //第一关键字排名为1~i的数有多少个 for(int i=n;i>=1;--i) sa[c[x[y[i]]]--]=y[i],y[i]=0; //因为y的顺序是按照第二关键字的顺序来排的 //第二关键字靠后的,在同一个第一关键字桶中排名越靠后 //基数排序 swap(x,y); //这里不用想太多,因为要生成新的x时要用到旧的,就把旧的复制下来,没别的意思 x[sa[1]]=1; num=1; for(int i=2;i<=n;++i) x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) ? num:++num; //因为sa[i]已经排好序了,所以可以按排名枚举,生成下一次的第一关键字 if(num==n) break; m=num; //这里就不用那个m了,因为都有新的编号了 } } inline void get_height() { int k=0; for(int i=1;i<=n;++i) rk[sa[i]]=i; for(int i=1;i<=n;++i) { if(rk[i]==1) continue;//第一名height为0 if(k) --k;//h[i]>=h[i-1]-1; int j=sa[rk[i]-1]; while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k]) ++k; height[rk[i]]=k;//h[i]=height[rk[i]]; } } int main() { return 0; }
这板子我能打错无数遍o(╥﹏╥)o
单字符串:
可重叠最长重复子串:height[i]的max
不可重叠最长重复子串(相似子串)
#include<bits/stdc++.h> using namespace std; const int maxn=2e4+10; char s[maxn]; int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn]; int n,m=150; inline void get_SA() { for(int i=1;i<=n;i++)++c[x[i]=s[i]]; for(int i=2;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[i]]--]=i; for(int k=1;k<=n;k<<=1) { int num=0; for(int i=n-k+1;i<=n;i++)y[++num]=i; for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k; for(int i=1;i<=m;i++)c[i]=0; for(int i=1;i<=n;i++)++c[x[i]]; for(int i=2;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0; swap(x,y); x[sa[1]]=1; num=1; for(int i=2;i<=n;i++)x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num; if(num==n)break; m=num; } } inline void get_height() { int k=0; for(int i=1;i<=n;i++)rk[sa[i]]=i; for(int i=1;i<=n;i++) { if(rk[i]==1)continue; if(k)--k; int j=sa[rk[i]-1]; while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++; height[rk[i]]=k; } } bool judge(int num) { int l=sa[1],r=sa[1]; for(int i=2;i<=n;i++) { if(height[i]>=num) { l=min(l,sa[i]); r=max(r,sa[i]); } else { l=r=sa[i]; } if(r-l>num)return true; } return false; } int main() { scanf("%d",&n); int a[maxn]={0}; for(int i=1;i<=n;i++) { scanf("%d",&a[i]); if(i!=1)s[i-1]=a[i]-a[i-1]+48; } n--; get_SA(); get_height(); int low=1,high=n,mid,ans=0; while(low<=high) { mid=(low+high)>>1; if(judge(mid)) { ans=mid; low=mid+1; } else { high=mid-1; } } printf("%d\n",ans+1); return 0; }
可重叠至少k次重复子串
#include<bits/stdc++.h> using namespace std; const int maxn=2e4+10; char s[maxn]; int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn]; int n,m=150,k; inline void get_SA() { for(int i=1;i<=n;i++)++c[x[i]=s[i]]; for(int i=2;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[i]]--]=i; for(int k=1;k<=n;k<<=1) { int num=0; for(int i=n-k+1;i<=n;i++)y[++num]=i; for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k; for(int i=1;i<=m;i++)c[i]=0; for(int i=1;i<=n;i++)++c[x[i]]; for(int i=2;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0; swap(x,y); x[sa[1]]=1; num=1; for(int i=2;i<=n;i++) x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num; if(num==n)break; m=num; } } inline void get_height() { int k=0; for(int i=1;i<=n;i++)rk[sa[i]]=i; for(int i=1;i<=n;i++) { if(rk[i]==1)continue; if(k)k--; int j=sa[rk[i]-1]; while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++; height[rk[i]]=k; } } bool judge(int num) { int cnt=1; for(int i=2;i<=n;i++) { if(height[i]>=num) { cnt++; } else { cnt=1; } if(cnt>=k)return true; } return false; } int main() { scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) { int num; scanf("%d",&num); s[i]=num+48; } get_SA(); get_height(); int low=1,high=n,mid,ans=0; while(low<=high) { mid=(low+high)>>1; if(judge(mid)) { ans=mid; low=mid+1; } else { high=mid-1; } } printf("%d\n",ans); return 0; }
不同字串个数:对n-sa[i]+1-height[i]求和
求第k大子串左右端点,hdu5008:
预处理每个后缀里有多少子串,找出第k大的子串第一次出现的后缀,之后二分+rmq找左右端点
https://blog.csdn.net/weixin_43093481/article/details/82875115
#include<bits/stdc++.h> using namespace std; #define ll long long const int maxn=1e5+5; char s[maxn]; int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn]; int n,m=131; ll sum[maxn],dp1[maxn][30],dp2[maxn][30]; inline void get_SA() { for(int i=1;i<=n;i++)++c[x[i]=s[i]]; for(int i=2;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[i]]--]=i; for(int k=1;k<=n;k<<=1) { int num=0; for(int i=n-k+1;i<=n;i++)y[++num]=i; for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k; for(int i=1;i<=m;i++)c[i]=0; for(int i=1;i<=n;i++)++c[x[i]]; for(int i=2;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0; swap(x,y); x[sa[1]]=1; num=1; for(int i=2;i<=n;i++) x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num; if(num==n)break; m=num; } } inline void get_height() { int k=0; for(int i=1;i<=n;i++)rk[sa[i]]=i; for(int i=1;i<=n;i++) { if(rk[i]==1)continue; if(k)k--; int j=sa[rk[i]-1]; while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++; height[rk[i]]=k; } } void cal() { sum[0]=0; for(int i=1;i<=n;i++) { sum[i]=sum[i-1]+n-sa[i]+1-height[i]; } return; } void rmq_init() { for(int i=1;i<=n;i++) { dp1[i][0]=height[i]; dp2[i][0]=sa[i]; } for(int j=1;(1<<j)<=n;j++) { for(int i=1;i+(1<<j)-1<=n;i++) { dp1[i][j]=min(dp1[i][j-1],dp1[i+(1<<j-1)][j-1]); dp2[i][j]=min(dp2[i][j-1],dp2[i+(1<<j-1)][j-1]); } } } ll rmq1(int l,int r) { int k=log2(r-l+1); return min(dp1[l][k],dp1[r-(1<<k)+1][k]); } ll rmq2(int l,int r) { int k=log2(r-l+1); return min(dp2[l][k],dp2[r-(1<<k)+1][k]); } int main() { //freopen("1.txt","r",stdin); //freopen("2.txt","w",stdout); scanf("%s",s+1); n=strlen(s+1); get_SA(); get_height(); cal(); rmq_init(); int q; ll lp=0,rp=0,k; scanf("%d",&q); while(q--) { scanf("%lld",&k); k^=lp,k^=rp,k++; if(k>sum[n]) { lp=rp=0; } else { int pos=lower_bound(sum+1,sum+1+n,k)-sum; k-=sum[pos-1]; ll len=height[pos]+k; int low=pos+1,high=n,mid,ans=pos; while(low<=high) { mid=(low+high)>>1; if(rmq1(pos+1,mid)>=len) { ans=mid; low=mid+1; } else { high=mid-1; } } ll tmp=rmq2(pos,ans); lp=tmp; rp=tmp+len-1; } printf("%lld %lld\n",lp,rp); } return 0; }
不可重叠重复子串个数
#include<bits/stdc++.h> using namespace std; const int maxn=1e3+10; char s[maxn]; int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn]; int n,m=131; inline void get_SA() { for(int i=1;i<=n;i++)++c[x[i]=s[i]]; for(int i=2;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[i]]--]=i; for(int k=1;k<=n;k<<=1) { int num=0; for(int i=n-k+1;i<=n;i++)y[++num]=i; for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k; for(int i=1;i<=m;i++)c[i]=0; for(int i=1;i<=n;i++)++c[x[i]]; for(int i=2;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0; swap(x,y); x[sa[1]]=1; num=1; for(int i=2;i<=n;i++)x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num; if(num==n)break; m=num; } } inline void get_height() { int k=0; for(int i=1;i<=n;i++)rk[sa[i]]=i; for(int i=1;i<=n;i++) { if(rk[i]==1)continue; if(k)--k; int j=sa[rk[i]-1]; while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++; height[rk[i]]=k; } } int judge(int num) { int l=sa[1],r=sa[1],cnt=0; for(int i=2;i<=n;i++) { if(height[i]>=num) { l=min(l,sa[i]); r=max(r,sa[i]); } else { if(r-l>=num)cnt++; l=r=sa[i]; } } if(r-l>=num)cnt++; return cnt; } int main() { scanf("%s",s+1); n=strlen(s+1); get_SA(); get_height(); int ans=0; for(int i=1;i<=n/2;i++) { ans+=judge(i); } printf("%d\n",ans); return 0; }
两个字符串:
最长公共子串:两串之间用‘$’连接
#include<bits/stdc++.h> using namespace std; const int maxn=2e5+10; char s[maxn]; int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn]; int n,m=131; inline void get_SA() { for(int i=1;i<=n;i++)++c[x[i]=s[i]]; for(int i=2;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[i]]--]=i; for(int k=1;k<=n;k<<=1) { int num=0; for(int i=n-k+1;i<=n;i++)y[++num]=i; for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k; for(int i=1;i<=m;i++)c[i]=0; for(int i=1;i<=n;i++)++c[x[i]]; for(int i=2;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0; swap(x,y); x[sa[1]]=1; num=1; for(int i=2;i<=n;i++)x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num; if(num==n)break; m=num; } } inline void get_height() { int k=0; for(int i=1;i<=n;i++)rk[sa[i]]=i; for(int i=1;i<=n;i++) { if(rk[i]==1)continue; if(k)--k; int j=sa[rk[i]-1]; while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++; height[rk[i]]=k; } } int main() { int n1,n2; scanf("%s",s+1); n1=strlen(s+1); s[n1+1]='$'; scanf("%s",s+n1+2); n2=strlen(s+1)-n1-1; n=n1+n2+1; get_SA(); get_height(); int ans=0; for(int i=2;i<=n;i++) { if(height[i]>ans) { if(sa[i]<n1+1&&sa[i-1]>n1+1||sa[i]>n1+1&&sa[i-1]<n1+1) ans=height[i]; } } printf("%d\n",ans); return 0; }
长度不小于K的公共子串个数
多个字符串:
其他串没有的子串
多串的最长公共子串
不小于个串的最长子串
每个串中至少出现两次且不重叠的子串个数
出现或反转出现在每个字符串的最长子串
hdu5343 sam后缀自动机+记忆化搜索
这个专题好难,不知道啥时候补...