后缀排序
题目链接: P3809【模板】后缀排序
sa[i]表示排名为i的后缀的起始位置的下标
#include<bits/stdc++.h> using namespace std; const int maxx = 1e6+10; char s[maxx]; int y[maxx],x[maxx],c[maxx],sa[maxx]; int rk[maxx],height[maxx],wt[30]; int n,m; 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]; 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; 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; //因为y的顺序是按照第二关键字的顺序来排的 //第二关键字靠后的,在同一个关键字桶中排名越靠后 //基数排序 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; } for(int i=1;i<=n;i++) printf("%d ",sa[i]); printf("\n"); } void getheight() { 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() { scanf("%s",s+1); n=strlen(s+1);m=122; get_sa(); return 0; }
求最大公共前缀
rk[i]就表示起始位置的下标为i的后缀的排名
height[i]表示排名为i-1的后缀和排名为i的后缀的最大公共前缀
height[i]有一个很重要的性质
定义LCP(i,j)为排名为i的后缀和排名为j的后缀的最大公共前缀
LCP(i,k)=min(LCP[i,i+1],LCP[i+1,i+2]...LCP[k-1,k])
LCP[i,k]=min(height[i+1],height[i+2]...height[k])
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; } }
后缀数组的应用
求两个字符串的最长公共子串
题目链接: poj2774 Long Long Message
思路:将两个字符串连起来,中间用特殊符号隔开,然后跑后缀数组,求满足sa[i-1]和sa[i]在特殊符号两边的height[i]的最大值
#include<iostream> #include<cstring> #include<cstdio> using namespace std; const int maxx = 1e6+10; char s[maxx]; int y[maxx],x[maxx],c[maxx],sa[maxx]; int rk[maxx],height[maxx]; int n,m; void get_sa() { for(int i=1;i<=m;i++)c[i]=0; 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; } } 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() { scanf("%s",s+1); int len1=strlen(s+1); s[++len1]='0'; scanf("%s",s+len1+1); int len2=strlen(s+1); m=122;n=len2; get_sa(); get_height(); int ans=0; for(int i=2;i<=len2;i++) { if(height[i]>ans) { if((len1<sa[i]&&len1>sa[i-1])||(len1>sa[i]&&len1<sa[i-1])) ans=height[i]; } } printf("%d\n",ans); return 0; }
求多个字符串的最长公共子串
题目链接: poj3450 Corporate Identity
思路:将每个字符串用特殊字符分隔连成一串,然后二分枚举长度,通过height[i]判断是否有n个字符串的最长公共子串为这个长度
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int maxx = 800010; char ch[210]; int y[maxx],x[maxx],c[maxx],sa[maxx]; int rk[maxx],height[maxx]; int s[maxx]; int pos[maxx],vis[4010]; int n,m,t,anspos; void get_sa() { for(int i=1;i<=m;i++)c[i]=0; 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; } } 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 mid) { int i=2; for(int i=2;i<=n;i++) { if(height[i]<mid)continue; int cnt=0; for(int j=1;j<=t;j++)vis[j]=0; while(height[i]>=mid&&i<=n) { if(!vis[pos[sa[i-1]]]) { vis[pos[sa[i-1]]]=1; cnt++; } i++; } if(!vis[pos[sa[i-1]]]) { vis[pos[sa[i-1]]]=1; cnt++; } if(cnt>=t) { anspos=sa[i-1]; return 1; } } return 0; } int main() { while(~scanf("%d",&t)&&t) { m=30; n=0; int len,ma=210; for(int i=1;i<=t;i++) { scanf("%s",ch); len=strlen(ch); ma=min(ma,len); for(int j=0;j<len;j++) { s[++n]=ch[j]-'a'+1; pos[n]=i; } s[++n]=++m; } get_sa(); get_height(); int l=1,r=ma; int ans=0; while(l<=r) { int mid=(l+r)/2; if(judge(mid))ans=mid,l=mid+1; else r=mid-1; } if(ans==0)printf("IDENTITY LOST\n"); else { for(int i=anspos;i<anspos+ans;i++) printf("%c",s[i]+'a'-1); printf("\n"); } } return 0; }
求多个字符串中不少于k个字符串的最长公共子串
题目链接:poj3294 Life Forms
思路:跟上面一样,判断的时候只要判断是否有超过n/2个字符串的的最长公共子串为该长度,这道题还要特判一下n=1
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int maxx = 1000010; char ch[1010]; int y[maxx],x[maxx],c[maxx],sa[maxx]; int rk[maxx],height[maxx]; int s[maxx]; int pos[maxx],vis[110]; int n,m,t,anspos[maxx]; void get_sa() { for(int i=1;i<=m;i++)c[i]=0; 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; } } 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 mid) { int i=2,tot=0; for(int i=2;i<=n;i++) { if(height[i]<mid)continue; int cnt=0; for(int j=1;j<=t;j++)vis[j]=0; while(height[i]>=mid&&i<=n) { if(!vis[pos[sa[i-1]]]) { vis[pos[sa[i-1]]]=1; cnt++; } i++; } if(!vis[pos[sa[i-1]]]) { vis[pos[sa[i-1]]]=1; cnt++; } if(cnt>t/2)anspos[++tot]=sa[i-1]; } if(tot) { anspos[0]=tot; return 1; } else return 0; } int main() { int cas=0; while(~scanf("%d",&t)&&t) { if(cas++)printf("\n"); if(t==1) { scanf("%s",ch); printf("%s\n",ch); continue; } m=30; n=0; int len,ma=0; for(int i=1;i<=t;i++) { scanf("%s",ch); len=strlen(ch); ma=max(ma,len); for(int j=0;j<len;j++) { s[++n]=ch[j]-'a'+1; pos[n]=i; } s[++n]=++m; } get_sa(); get_height(); int l=1,r=ma; int ans=0; while(l<=r) { int mid=(l+r)/2; if(judge(mid))ans=mid,l=mid+1; else r=mid-1; } if(ans==0)printf("?\n"); else { for(int i=1;i<=anspos[0];i++) { for(int j=anspos[i];j<anspos[i]+ans;j++) printf("%c",s[j]+'a'-1); printf("\n"); } } } return 0; }