1. 程式人生 > >BZOJ 2946 POI2000 公共串 後綴自動機(多串最長公共子串)

BZOJ 2946 POI2000 公共串 後綴自動機(多串最長公共子串)

調整 log spa size 暴力 pan emc auto 結束

題意概述:給出N個字符串,每個串的長度<=2000(霧。。。可能是當年的年代太久遠機子太差了),問這N個字符串的最長公共子串長度為多少。(N<=5)

拋開數據結構,先想想樸素做法。

設計一種穩定的暴力算法。可以想到這樣一種做法:首先確定一個串,枚舉每個位置,然後暴力計算其他每個串以這個位置開頭的最長匹配,取最小值,就是在公共子串在我們確定下來的串的這個位置開頭的時候所能得到的最長公共子串。不難發現把這個問題轉化成後綴的形式也是一樣的。同時發現可能在枚舉多個位置的時候答案甚至最後構造出來的串都是一模一樣的。

所以就有了SAM計算最長公共子串的做法(實際上後綴數組也可以做就是可憐的多了個log)。

首先建立第一個串的SAM,然後用剩下的串在上面跑,計算每個串在所有狀態上的最長匹配。從初始狀態一路走下來的路徑就是一個公共子串,而現在經歷的邊數就是你在這個狀態下的最長匹配長度;失配的時候就沿著pa一直調整,根據SAM的原理,初始狀態到pa[i]的所有路徑一定是初始狀態到i的所有路徑形成的字符串的後綴,並且是盡量長的。當在i的祖先狀態p找到一條可以匹配的邊的時候就重新出發,把當前的最長匹配的長度設成MAX(p)(len-MAX(p)那一段導致匹配失敗了,丟掉)。

根據上面暴力的做法,我們應該要統計在每個位置作為公共子串結束位置的時候的最長匹配,於是這個時候就要在parent樹中自下而上用每個狀態上的最長匹配更新了。雖然一個狀態的right裏面有一堆r,但是在理解到SAM只是對所有後綴的壓縮儲存之後我們只要最大的那個就可以了。利用topo序可以做到線性時間內完成。

整個算法的時間復雜度是O(N*L),N變成100000都沒有什麽大毛病?(對gets的深深恐懼,我以後再也不隨便用gets了QWQ)

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cstdlib>
 5 #include<algorithm>
 6 #include<cmath>
 7 #include<queue>
 8 #include<set>
 9 #include<map>
10
#include<vector> 11 #include<cctype> 12 using namespace std; 13 const int maxn=2005; 14 15 int N; char S[maxn]; 16 struct Suffix_Automaton{ 17 static const int maxn=4005; 18 static const int sigma_sz=26; 19 int sz,last,to[maxn][sigma_sz],mx[maxn],pa[maxn],match[maxn],f[maxn]; 20 int topo[maxn],c[maxn]; 21 Suffix_Automaton(){ 22 sz=last=1; memset(to[1],0,sizeof(to[1])); 23 } 24 int newnode(){ 25 memset(to[++sz],0,sizeof(to[sz])); 26 mx[sz]=pa[sz]=0; 27 return sz; 28 } 29 void extend(int w){ 30 int p=last,np=newnode(); last=np; 31 mx[np]=mx[p]+1; 32 while(p&&!to[p][w]) to[p][w]=np,p=pa[p]; 33 if(!p) pa[np]=1; 34 else{ 35 int q=to[p][w]; 36 if(mx[q]==mx[p]+1) pa[np]=q; 37 else{ 38 int nq=newnode(); mx[nq]=mx[p]+1; 39 memcpy(to[nq],to[q],sizeof(to[q])); 40 pa[nq]=pa[q]; 41 pa[q]=pa[np]=nq; 42 while(p&&to[p][w]==q) to[p][w]=nq,p=pa[p]; 43 } 44 } 45 } 46 void ready(int n){ 47 memset(c,0,sizeof(c)); 48 for(int i=1;i<=sz;i++) c[mx[i]]++; 49 for(int i=1;i<=n;i++) c[i]+=c[i-1]; 50 for(int i=sz;i>=1;i--) topo[c[mx[i]]--]=i; 51 } 52 void run(char *s){ 53 memset(f,0,sizeof(f)); 54 int i=0,p=1,len=0,n=strlen(s); 55 while(i<n){ 56 int j=s[i]-a; 57 while(p&&!to[p][j]) p=pa[p],len=mx[p]; 58 if(!p) p=1,len=0; 59 else p=to[p][j],f[p]=max(f[p],++len); 60 i++; 61 } 62 for(int i=sz;i>=1;i--){ 63 int x=topo[i]; 64 f[pa[x]]=max(f[pa[x]],f[x]); 65 } 66 for(int i=1;i<=sz;i++) 67 match[i]=min(match[i],f[i]); 68 } 69 }sam; 70 71 void work() 72 { 73 scanf("%d%s",&N,S); 74 int n=strlen(S); 75 for(int i=0;i<n;i++) sam.extend(S[i]-a); 76 sam.ready(n); 77 for(int i=1;i<=sam.sz;i++) sam.match[i]=sam.mx[i]; 78 for(int i=1;i<N;i++) { scanf("%s",S); sam.run(S); } 79 int ans=0; 80 for(int i=1;i<=sam.sz;i++) 81 ans=max(ans,sam.match[i]); 82 printf("%d\n",ans); 83 } 84 int main() 85 { 86 work(); 87 return 0; 88 }

BZOJ 2946 POI2000 公共串 後綴自動機(多串最長公共子串)