SPOJ1812: LCS2 - Longest Common Substring II & BZOJ2946: [Poi2000]公共串
【传送门:SPOJ1811&BZOJ2946】
简要题意:
给出若干个字符串,求出这些字符串的最长公共子串
题解:
后缀自动机
这两道题的区别只是在于一道给出了字符串个数,一个没给,不过也差不多(代码就贴SPOJ的,因为数据范围大一点)
首先用第一个字符串构造SAM
然后处理其他的每个串在后缀自动机的每个状态能够匹配的子串最大值
然后总的来处理所有串在这个状态能够匹配的最小值
最后将所有状态都枚举一遍,求出其中的最大值就行了
参考代码:
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; struct SAM { int son[27],fail,dep; }tr[210000];int root,cnt,last; int a[110000]; void add(int k) { int x=a[k]; int np=++cnt,p=last; tr[np].dep=k; while(p!=0&&tr[p].son[x]==0) tr[p].son[x]=np,p=tr[p].fail; if(p==0) tr[np].fail=root; else { int q=tr[p].son[x]; if(tr[q].dep==tr[p].dep+1) tr[np].fail=q; else { int nq=++cnt;tr[nq]=tr[q]; tr[nq].dep=tr[p].dep+1; tr[q].fail=tr[np].fail=nq; while(p!=0&&tr[p].son[x]==q) tr[p].son[x]=nq,p=tr[p].fail; } } last=np; } char st[110000]; int Rsort[210000],sa[210000]; int d[210000],f[210000]; int main() { scanf("%s",st+1); int len=strlen(st+1); cnt=root=last=1; for(int i=1;i<=len;i++) a[i]=st[i]-'a'+1,add(i); for(int i=1;i<=cnt;i++) Rsort[tr[i].dep]++; for(int i=1;i<=len;i++) Rsort[i]+=Rsort[i-1]; for(int i=cnt;i>=1;i--) sa[Rsort[tr[i].dep]--]=i; memset(f,63,sizeof(f)); while(scanf("%s",st+1)!=EOF) { len=strlen(st+1); memset(d,0,sizeof(d)); for(int i=1;i<=len;i++) a[i]=st[i]-'a'+1; int p=root,s=0,ans=0; for(int i=1;i<=len;i++) { if(tr[p].son[a[i]]!=0) { s++; p=tr[p].son[a[i]]; } else { while(p!=0&&tr[p].son[a[i]]==0) p=tr[p].fail; if(p==0) p=root,s=0; else { s=tr[p].dep+1,p=tr[p].son[a[i]]; } } d[p]=max(d[p],s); } for(int i=cnt;i>=1;i--) d[tr[sa[i]].fail]=min(tr[tr[sa[i]].fail].dep,max(d[tr[sa[i]].fail],d[sa[i]])); for(int i=cnt;i>=1;i--) f[sa[i]]=min(f[sa[i]],d[sa[i]]); } int ans=0; for(int i=1;i<=cnt;i++) ans=max(f[i],ans); printf("%d\n",ans); return 0; }
渺渺时空,茫茫人海,与君相遇,幸甚幸甚