BZOJ_2946_[Poi2000]公共串_后缀数组+二分答案
BZOJ_2946_[Poi2000]公共串_后缀数组+二分答案
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
先把所有串拼起来,中间用分隔符分开。
二分答案x,转化为判定是否有一个长度为x的公共子串。
然后把后缀分组,每组内height都大于等于x。
只需要判断是否存在一组出现了所有的原串。
代码:
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define N 10050 int ws[N],wa[N],wb[N],wv[N],r[N],sa[N],height[N],rank[N],n,m,T,pos[N],h[10]; char str[N]; void build_suffix_array() { m=T+27; int i,j,p,*x=wa,*y=wb,*t; for(i=0;i<m;i++) ws[i]=0; for(i=0;i<n;i++) ws[x[i]=r[i]]++; for(i=1;i<m;i++) ws[i]+=ws[i-1]; for(i=n-1;i>=0;i--) sa[--ws[x[i]]]=i; for(p=j=1;p<n;j<<=1,m=p) { for(p=0,i=n-j;i<n;i++) y[p++]=i; for(i=0;i<n;i++) if(sa[i]-j>=0) y[p++]=sa[i]-j; for(i=0;i<n;i++) wv[i]=x[y[i]]; for(i=0;i<m;i++) ws[i]=0; for(i=0;i<n;i++) ws[wv[i]]++; for(i=1;i<m;i++) ws[i]+=ws[i-1]; for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i]; for(t=x,x=y,y=t,x[sa[0]]=0,i=p=1;i<n;i++) { if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+j]==y[sa[i-1]+j]) x[sa[i]]=p-1; else x[sa[i]]=p++; } } for(i=1;i<n;i++) rank[sa[i]]=i; for(i=p=0;i<n-1;height[rank[i++]]=p) for(p?p--:0,j=sa[rank[i]-1];r[i+p]==r[j+p];p++) ; } bool full() { int i; for(i=1;i<=T;i++) if(!h[i]) return 0; return 1; } bool check(int x) { int i,j; for(i=1;i<n;i++) { if(height[i]<x) { memset(h,0,sizeof(h)); } h[pos[sa[i]]]++; if(full()) return 1; } return 0; } int main() { scanf("%d",&T); int i,j; for(i=1;i<=T;i++) { scanf("%s",str); for(j=0;str[j];j++) r[n]=str[j]-'a'+1,pos[n++]=i; r[n++]=26+i; } n++; build_suffix_array(); int l=0,ri=n+1; while(l<ri) { int mid=(l+ri)>>1; if(check(mid)) l=mid+1; else ri=mid; } printf("%d\n",l-1); }