POJ 2774求兩字符串的最長公共子串(後綴數組)
題目鏈接:傳送門
思路:
大概就是枚舉s2(s1)的每個後綴,查看對應的s1(s2)的後綴與自己的最長前綴長度是多少。
我們可以把字符串s1和s2拼接到一起,中間用’#‘連接(’#'只是代表比字符集任意一個字符小的字符),然後求出拼接後字符串的後綴數組,在排序後的後綴數組上排名從小到大遍歷,並同時記下左邊最近的s1(s2)的後綴的位置p1(p2),然後對於每個s2(s1)的後綴,用RMQ求出p1(p2)位置的後綴串與自己後綴的最長前綴長度是多少(這一步可以RMQ預處理O(1)查詢區間最小值實現),更新最長前綴長度的最大值即可。
代碼:
//#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);
}