后缀数组的应用
后缀排序
题目链接: 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;
}