1. 程式人生 > >SPOJ1811 LCS - Longest Common Substring 字尾自動機

SPOJ1811 LCS - Longest Common Substring 字尾自動機

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

題意:
給你兩個長度不超過250000的字串,求兩個串的最長公共子串。

題解:
看起來是個比較經典的問題。似乎SAM處理子串的能力很強啊。

做法是先對第一個串建出SAM,然後我們考慮parent樹的含義,其實我覺得在某種意義下parent樹可以理解為AC自動機的fail指標,因為它保證了子節點表示的字串集合是父節點的一個真子集,所以如果在子節點匹配不上的話,下一個應該嘗試是否能在parent樹上這個點的父節點匹配,所以在這裡可以當作fial指標使用。那麼這道題就比較簡單了,我們只需要列舉第二個串的每一個字元,然後看之前跳到了哪個位置,如果有當前字元這個出邊,那麼就走過去,否則就順著parent樹網上跳,直到有這樣的出邊或者到根為止。對於每一個在第二個串中的字元,最後停留在的位置的字串長度就是在第二個串到當前字元為止的一個最長的能與第一個串匹配的一個字尾的長度,每次用這個長度更新答案即可。

程式碼:

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

int n,ch[500010][27],lst=1,fa[500010],cnt=1,rt=1,len[500010],ans; 
char s[250010],s1[250010];
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; len[gg]=len[pre]+1; memcpy(ch[gg],ch[ji],sizeof(ch[ji])); fa[gg]=fa[ji]; fa[ji]=fa[cur]=gg; for(;pre&&ch[pre][x]==ji;pre=fa[pre]) ch[pre][x]=gg; } } } int main() { scanf
("%s",s+1); scanf("%s",s1+1); n=strlen(s+1); for(int i=1;i<=n;++i) insert(s[i]-'a'); n=strlen(s1+1); int cur=rt,l=0; for(int i=1;i<=n;++i) { int x=s1[i]-'a'; if(ch[cur][x]) { l++; cur=ch[cur][x]; ans=max(ans,l); continue; } for(;cur&&!ch[cur][x];cur=fa[cur]); if(cur) { l=len[cur]+1; cur=ch[cur][x]; } else { cur=1; l=0; } ans=max(l,ans); } printf("%d\n",ans); return 0; }