1. 程式人生 > >SPOJ1812 LCS2 POI2000 BZOJ2946 公共串 SAM

SPOJ1812 LCS2 POI2000 BZOJ2946 公共串 SAM

題目連結
連結是洛谷有翻譯的。

題意:
給定一些字串,求出它們的最長公共子串。輸入至多10行,每行包含不超過100000個的小寫字母。

這題似乎和POI2000BZOJ2946是同一道題,於是我就也掛上那個標籤了。PS:BZOJ的那個是個許可權題。。。

題解:
我們之前做過兩個串通過SAM找最長公共子串的,具體請看這裡
那麼我們考慮多個串的做法,其實多個串是可以從兩個串拓展來的。思路上就是我們對第一個串建出SAM,然後其他串與這個串匹配,我們對於當前串,算出每個位置能匹配的最大值,然後對於所有的串,我們要對於每個位置取min,因為最小的才能是公共的。具體的實現的話就是每次拿出一個串,像兩個串那樣在SAM上跑,跑完之後根據parent樹更新一下parent樹上的點的答案。然後再對每個位置取min。寫的時候對於一個字串,處理它的時候維護一個每個位置的max,對於所有的串,維護一個每個位置的min。最後答案是所有的min中的max。說起來好像挺繞的,但是其實體會一下還是不難理解的。

程式碼:

#include <bits/stdc++.h>
using namespace std;

int n,fa[400010],len[400010],rt=1,cnt=1,lst=1,ch[400010][26];
int hed[400010],num,ans,mn[400010],mx[400010];
struct node
{
	int to,next;
}a[800010];
char s[100010];
inline void insert(int x)
{
	int cur=++cnt,pre=lst;
	lst=cur;
	len[cur]=len[pre]+1;
	for(;pre&&!ch[
pre][x];pre=fa[pre]) ch[pre][x]=cur; if(!pre) fa[cur]=rt; else { int ji=ch[pre][x]; if(len[ji]==len[pre]+1) fa[cur]=ji; else { int gg=++cnt; memcpy(ch[gg],ch[ji],sizeof(ch[ji])); fa[gg]=fa[ji]; fa[ji]=fa[cur]=gg; len[gg]=len[pre]+1; for(;pre&&ch[pre][x]==ji;pre=
fa[pre]) ch[pre][x]=gg; } } } inline void add(int from,int to) { a[++num].to=to; a[num].next=hed[from]; hed[from]=num; } inline void dfs(int x) { for(int i=hed[x];i;i=a[i].next) { int y=a[i].to; dfs(y); mx[x]=max(mx[x],min(mx[y],len[x])); } } inline void qwq() { int l=0,cur=rt; memset(mx,0,sizeof(mx)); for(int i=1;i<=n;++i) { int x=s[i]-'a'; if(ch[cur][x]) { cur=ch[cur][x]; ++l; } else { for(;cur&&!ch[cur][x];cur=fa[cur]); if(!cur) { cur=rt; l=0; } else { l=len[cur]+1; cur=ch[cur][x]; } } mx[cur]=max(mx[cur],l); } dfs(1); for(int i=1;i<=cnt;++i) mn[i]=min(mn[i],mx[i]); } int main() { scanf("%s",s+1); n=strlen(s+1); for(int i=1;i<=n;++i) insert(s[i]-'a'); for(int i=2;i<=cnt;++i) add(fa[i],i); memset(mn,0x3f,sizeof(mn)); while(~scanf("%s",s+1)) { n=strlen(s+1); qwq(); } for(int i=1;i<=cnt;++i) ans=max(ans,mn[i]); printf("%d\n",ans); return 0; }