Description
给出几个由小写字母构成的单词,求它们最长的公共子串的长度。
任务:
l
读入单词
l
计算最长公共子串的长度
l
输出结果
Input
文件的第一行是整数
n
,
1<=n<=5
,表示单词的数量。接下来
n
行每行一个单词,只由小写字母组成,单词的长度至少为
1
,最大为
2000
。
Output
仅一行,一个整数,最长公共子串的长度。
Sample Input
3
abcb
bca
acbc
abcb
bca
acbc
Sample Output
2
HINT
Source
基本上算是一个很常见的套路了,将所有出现过的字符串连接成一个,中间用一个从来都没有出现过的字符隔开,然后构造sa数组和h数组最后二分答案,On的check,只用保证一段连续后缀h长度>=x并且这之中所有的串都出现过就好了
#include<cstdio>
#include<cstring>
#include<iostream>
#define maxn 1000020
using namespace std;
int n,sa[maxn],h[maxn],rank[maxn],c[maxn],t1[maxn],t2[maxn],bel[maxn],N;
char s[maxn];
inline bool cmp(int* y,int a,int b,int k){
int aa=a+k>=n?-1:y[a+k];
int bb=b+k>=n?-1:y[b+k];
return y[a]==y[b]&&aa==bb;
}
void getsa(int n,int m){
int p=0,i,*x=t1,*y=t2;
for(i=0;i<n;i++)c[x[i]=s[i]]++;
for(i=0;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
for(int k=1;k<=n;k<<=1){
p=0;
for(i=n-k;i<n;i++)y[p++]=i;
for(i=0;i<n;i++)if(sa[i]>=k)y[p++]=sa[i]-k;
for(i=0;i<=m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[y[i]]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
swap(x,y);p=1,x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i],sa[i-1],k)?p-1:p++;
if(p>=n)break;
m=p;
}
}
void get_h(int n){
for(int i=0;i<n;i++)rank[sa[i]]=i;
for(int j,k=0,i=0;i<n;i++){
if(k)k--;
if(rank[i]==0)continue;
j=sa[rank[i]-1];
while(s[i+k]==s[j+k])k++;
h[rank[i]]=k;
}
}
bool is[maxn];
bool check(int x){
bool ok=true;
for(int i=1;i<=N;i++)is[i]=false;
for(int i=0;i<=n;i++){
if(h[i]<x||i==n){
for(int j=1;j<=N;j++)if(!is[j]){ok=false;break;}
if(ok)return true;
ok=true;
for(int j=1;j<=N;j++)is[j]=false;
}else{
is[bel[sa[i]]]=is[bel[sa[i-1]]]=true;
}
}
return false;
}
void solve(){
int l=0,r=n,ans;
while(l<=r){
int mid=l+r>>1;
if(check(mid))l=mid+1,ans=mid;
else r=mid-1;
}
printf("%d",ans);
}
int main(){
scanf("%d",&n);N=n;
for(int tmp=0,i=1;i<=N;i++){
scanf("%s",s+tmp);
tmp+=strlen(s+tmp);
s[tmp++]='z'+i;
n=tmp;
}
for(int cnt=1,i=0;i<n;i++){
if(s[i]>'z'||s[i]<'a')cnt++;
if(s[i]>='a'&&s[i]<='z')bel[i]=cnt;
}
getsa(n,500);
get_h(n);
solve();
return 0;
}