POJ 2774求两字符串的最长公共子串(后缀数组)
题目链接:传送门
思路:
大概就是枚举s2(s1)的每个后缀,查看对应的s1(s2)的后缀与自己的最长前缀长度是多少。
我们可以把字符串s1和s2拼接到一起,中间用’#‘连接(’#'只是代表比字符集任意一个字符小的字符),然后求出拼接后字符串的后缀数组,在排序后的后缀数组上排名从小到大遍历,并同时记下左边最近的s1(s2)的后缀的位置p1(p2),然后对于每个s2(s1)的后缀,用RMQ求出p1(p2)位置的后缀串与自己后缀的最长前缀长度是多少(这一步可以RMQ预处理O(1)查询 h e i g h t [ ] height[] height[]区间最小值实现),更新最长前缀长度的最大值即可。
代码:
//#include<bits/stdc++.h>
#include<algorithm>
#include<stdio.h>
#include<iostream>
#include<string.h>
#define mset(a,b) memset(a,b,sizeof(a))
typedef long long ll;
const int N=2e5+10;
int t1[N],t2[N],c1[N];//辅助数组
void SA(char *s,int n,int sa[],int rank[],int height[])//基数排序的版本中
{
int m=128;//桶的大小,会在下面循环中变化,第一关键词r的最大值,初始时是字符的最大值,之后都<=n
int *sb=t1,*r=t2,*c=c1;//辅助数组,分别为:基数排序所用的第二2,rak',cnt数组
//用基数排序求出长度为1的sa[]和rank[],如果字符最大值较大,第一轮可以采用sort
for(int i=0; i<=m; ++i) c[i]=0;
for(int i=1; i<=n; ++i) c[r[i]=s[i]]++;
for(int i=1; i<=m; ++i) c[i]+=c[i-1];
for(int i=n; i>=1; --i) sa[c[s[i]]--]=i;
for(int k=1,p ; k < n; k<<=1 ) //p是一个计数器,现在还没用。
{
//sb[i]:第二关键词排名为i的位置为sb[i]
p=0;
for(int i=n-k+1; i<=n; ++i) sb[++p]=i;
for(int i=1; i<=n; ++i) if(sa[i]>k) sb[++p]=sa[i]-k;
//基数排序求出2k长度的sa数组
for(int i=0; i<=m; ++i) c[i]=0;
for(int i=1; i<=n; ++i) c[r[i]]++;
for(int i=1; i<=m; ++i) c[i]+=c[i-1];
for(int i=n; i>=1; --i) sa[ c[r[sb[i]]]-- ]=sb[i];
std::swap(r,sb);
//现在要利用上轮的r和这轮的sa求这轮的r
r[sa[1]]=p=1;
for(int i=2,a,b; i<=n; ++i)
{
a=sa[i],b=sa[i-1];
if(sb[a]==sb[b]&&(a+k<=n && b+k<=n&&sb[a+k]==sb[b+k]) ) r[a]=p;
else r[a]=++p;
}
if(p>=n) break;//可以提前退出
m=p;
}
/*计算高度数组*/
int k=0;
for(int i=1; i<=n; ++i) rank[sa[i]]=i;
height[1]=0;
for(int i=1; i<=n; ++i)
{
if(k) k--;
int j=sa[rank[i]-1];
if(j==0) continue;
while(s[j+k]==s[i+k]) ++k;
height[rank[i]]=k;
}
}
int rank[N],sa[N],height[N]; //height[1]的值为0
char s[N];
//预处理O(nlogn)—查询O(1)求height[]的连续最小值的RMQ代码
int RMQ[N],mm[N],best[N][20];//rmq[]=height[]
void initRMQ(int n, int height[])
{
for(int i=1; i<=n; ++i) RMQ[i]=height[i];
mm[0]=-1;
for(int i=1; i<=n; ++i) mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
for(int i=1; i<=n; ++i) best[i][0]=i;
for(int j=1; j<=mm[n]; ++j)
for(int i=1; i+(1<<j)-1<=n; ++i)
{
int a=best[i][j-1];
int b=best[i+(1<<(j-1))][j-1];
if(RMQ[a] < RMQ[b]) best[i][j]=a;
else best[i][j]=b;
}
}
int askRMQ(int a,int b)//求区间[a,b]最小值的下标
{
int t;
t=mm[b-a+1];
b-=(1<<t)-1;
a=best[a][t],b=best[b][t];
if(RMQ[a] < RMQ[b]) return a;
else return b;
}
int lcp(int a,int b)
{
a=rank[a],b=rank[b];
if(a > b) std::swap(a,b);
return height[askRMQ(a+1,b)];
}
char s1[N],s2[N];
int main()
{
int n=0;
scanf("%s%s",s1+1,s2+1);
int ls1=strlen(s1+1),ls2=strlen(s2+1);
for(int i=1; i<=ls1; ++i) s[++n]=s1[i];
s[++n]='#';
for(int i=1; i<=ls2; ++i) s[++n]=s2[i];
s[n+1]='\0';
SA(s,n,sa,rank,height);
initRMQ(n,height);
int lp1=-1,lp2=-1,ans=0;
for(int i=1; i<=n; ++i)
{
if(sa[i]>ls1+1)//是串s2
{
if(lp1!=-1)
ans=std::max(ans,height[askRMQ(lp1+1,i)]);
lp2=i;
}
else if(sa[i]<=ls1)
{
if(lp2!=-1)
ans=std::max(ans,height[askRMQ(lp2+1,i)]);
lp1=i;
}
}
printf("%d\n",ans);
}