题目大意:给n个字符串,求最长字符串长度,满足该字符串或其反转是这n个字符串的子串。
后缀数组。。将这n个字符串反转后拼接,就是求n个串的最长公共子串啦。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<set>
#include<vector>
using namespace std;
const int N=30000;
int s[N],sa[N],r[N],h[N],c[N],x[N],y[N],be[N],len;
char ss[N];
set<int>se;
void build(int n,int m){
int i,j,k;
for(i=0;i<m;i++) c[i]=0;
for(i=0;i<n;i++){
x[i]=s[i];
c[x[i]]++;
}
for(i=1;i<m;i++) c[i]+=c[i-1];
for(i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
for(j=1;j<n;j*=2){
k=0;
for(i=n-j;i<n;i++) y[k++]=i;
for(i=0;i<n;i++) if(sa[i]>=j) y[k++]=sa[i]-j;
for(i=0;i<m;i++) c[i]=0;
for(i=0;i<n;i++) c[x[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);
m=0;
x[sa[0]]=m++;
for(i=1;i<n;i++){
if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+j]==y[sa[i-1]+j]) x[sa[i]]=m-1;
else x[sa[i]]=m++;
}
if(m==n) break;
}
for(i=0;i<n;i++) r[sa[i]]=i;
k=0;
for(i=0;i<n-1;i++){
if(k) k--;
j=sa[r[i]-1];
while(s[i+k]==s[j+k]) k++;
h[r[i]]=k;
}
// for(i=0;i<n;i++) cout<<h[i]<<endl;
}
int f(int x,int k){
int i;
se.clear();
for(i=1;i<len;i++){
if(h[i]>=x){ //判断长度为x的子串是否在n个字符串中出现,
se.insert(be[sa[i]]);
se.insert(be[sa[i-1]]);
}
else{
if(se.size()==k) return 1;
se.clear();
}
}
return 0;
}
int main(){
int t,i,n,m,j;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
memset(be,0,sizeof(be));
len=0;
for(i=1;i<=n;i++){ //拼接n个字符串及其反转,中间用不重复出现的字符连接
scanf("%s",&ss);
m=strlen(ss);
for(j=0;j<m;j++){
be[len]=i;
s[len++]=ss[j];
}
s[len++]=1000+i*2;
for(j=m-1;j>=0;j--){
be[len]=i;
s[len++]=ss[j];
}
s[len++]=1000+i*2+1;
}
s[len-1]=0;
build(len,2000);
int l=0,r=len,mid; //二分最大长度
while(l+1<r){
mid=(l+r)/2;
if(f(mid,n)) l=mid;
else r=mid;
}
printf("%d\n",l);
}
return 0;
}