后綴家族已知成員
后綴樹
后綴數組
后綴自動機
后綴仙人掌
后綴預言
后綴Splay ?
后綴樹是后綴數組和后綴自動機的祖先? 功能還是比較強大的,在回文串或者字典序方面還是有用處。 而且現在已經有了線性的建樹方法。
(但其實我也沒用過后綴樹。)下面對比后綴自動機和后綴數組
-
單個字符串問題 不等號是“優於”,&&是差不多(以下是個人感覺)
- 1重復子串
- 1,1 可交叉最長重復子串 后綴自動機>=后綴數組 都是基本題,但是前者代碼稍短
- 1,2 不可交叉最長重復子串 后綴數組>=后綴自動機 前者易於判斷交叉;后者則需要記錄每個狀態所有出現的位置
- 1,3 可交叉的k次最長重復子串 后綴自動機>=后綴數組 后者需+二分判定;前者無需判斷,直接拓撲出每個狀態的次數
- 2子串個數問題
- 2,1 不相同子串個數 后綴自動機&&后綴數組 都是基本功能,易於實現。
- 3循環子串問題
- 3,1 求最小循環節 后綴數組,kmp 后綴自動機應該不行。
- 3,2 重復次數最多的連續重復子串 后綴數組
- 1重復子串
-
兩個字符串串問題
- 1公共子串問題
- 1,1 最長公共子串 后綴自動機&&后綴數組 都是基本功能
- 2子串個數問題
- 2,1 特定長度的公共子串 后綴自動機&&后綴數組 二者的基本功能
- 1公共子串問題
-
多個字符串問題
- 1公共子串問題
- 1,1 在k個字符串中的出現的最長子串 廣義后綴自動機>=后綴數組(KMP也可以求多個串的最長公共字串) (具體效率誰高取決於數據)
- 1,2 在每個字符串中出現k次的最長子串 廣義后綴自動機>=后綴數組
- 1,3 在每個字符串中或反轉后出現的最長子串 廣義后綴自動機?后綴數組
- 1公共子串問題
-
其他
- 最小表示法: 后綴自動機
- 最小循環節 :后綴數組
個人感覺:
單串和兩串的題,基本上用后綴數組或者后綴自動機都可以實現。多串的題用廣義后綴自動機也是非常強的,有點題如果要用后綴數組,則必須要用RMQ(樹狀數組||ST)+二分,甚至要用Splay來解決。當然靈活的運用后綴數組加各種工具來解決問題,才能應對各種難題,畢竟后綴自動機也是有局限的。個人更傾向於寫后綴自動機,感覺好實現一點,代碼也好看一點。
下面對比一下多串字符串的處理
廣義后綴自動機的題:
POJ3294: 題意:給定一些模板字符串,求一個最長公共字串,這個最長公共字串至少在一半以上的字符串里出現過。
對比:如果是后綴數組,則要+二分+RMQ;而廣義后綴自動機只需要記錄出現的位置,最后傳遞即可。

#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<memory> #include<cmath> #define maxn 350003 using namespace std; int n,len,ans,Max,now; char s[1010],cap[1010]; struct SAM { int ch[maxn][26],fa[maxn],maxlen[maxn],Last,sz; int root,nxt[maxn],size[maxn]; void init() { sz=0; root=++sz; memset(size,0,sizeof(size)); memset(ch[1],0,sizeof(ch[1])); memset(nxt,0,sizeof(nxt)); } void add(int x) { int np=++sz,p=Last;Last=np; memset(ch[np],0,sizeof(ch[np])); maxlen[np]=maxlen[p]+1; while(p&&!ch[p][x]) ch[p][x]=np,p=fa[p]; if(!p) fa[np]=1; else { int q=ch[p][x]; if(maxlen[p]+1==maxlen[q]) fa[np]=q; else { int nq=++sz; memcpy(ch[nq],ch[q],sizeof(ch[q]));size[nq]=size[q]; nxt[nq]=nxt[q]; maxlen[nq]=maxlen[p]+1; fa[nq]=fa[q]; fa[q]=fa[np]=nq; while(p&&ch[p][x]==q) ch[p][x]=nq,p=fa[p]; } } for(;np;np=fa[np]) if(nxt[np]!=now) { size[np]++; nxt[np]=now; }else break; } void dfs(int x,int d){//輸出 if(d!=maxlen[x] || d>ans) return; if(maxlen[x]==ans && size[x]>n){ puts(cap); return; } for(int i=0;i<26;++i) if(ch[x][i]){ cap[d]=i+'a'; dfs(ch[x][i],d+1); cap[d]=0; } } }; SAM Sam; int main() { while(~scanf("%d",&n)&&n){ Sam.init(); for(int i=1;i<=n;i++) { scanf("%s",s+1); Sam.Last=Sam.root; len=strlen(s+1); now=i; for(int j=1;j<=len;j++) Sam.add(s[j]-'a'); } Max=0;ans=0;n>>=1; for(int i=1;i<=Sam.sz;i++) if(Sam.size[i]>n&&Sam.maxlen[i]>ans) { Max=i;ans=Sam.maxlen[i];} if(ans) Sam.dfs(1,0); else puts("?"); puts(""); } return 0; }
SPOJ8093 題意:給定一些模板串,詢問每個匹配串在多少個模板串里出現過。
對比:同上。傳遞的兩種方式:每加一個字符傳遞一次;也可以用bitset記錄在哪里出現過等到加完所有字符串后再拓撲排序,然后“亦或”向上傳遞。

#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #define N 200003 using namespace std; int ch[N][30],fa[N],l[N],n,m,len; int r[N],v[N],cnt,np,p,nq,q,last,root,nxt[N],now,size[N]; char s[N]; void extend(int x) { int c=s[x]-'a'; p=last; np=++cnt; last=np; l[np]=l[p]+1; for (;p&&!ch[p][c];p=fa[p]) ch[p][c]=np; if (!p) fa[np]=root; else { q=ch[p][c]; if (l[q]==l[p]+1) fa[np]=q; else { nq=++cnt; l[nq]=l[p]+1; memcpy(ch[nq],ch[q],sizeof ch[nq]); size[nq]=size[q]; nxt[nq]=nxt[q]; fa[nq]=fa[q]; fa[q]=fa[np]=nq; for (;ch[p][c]==q;p=fa[p]) ch[p][c]=nq; } } for (;np;np=fa[np]) if (nxt[np]!=now) { size[np]++; nxt[np]=now; } else break; } int main() { scanf("%d%d",&n,&m); root=++cnt; for(int i=1;i<=n;i++) { scanf("%s",s+1); last=root; len=strlen(s+1); now=i; for (int j=1;j<=len;j++) extend(j); } for (int i=1;i<=m;i++) { scanf("%s",s+1); len=strlen(s+1); p=root; for (int j=1;j<=len;j++) p=ch[p][s[j]-'a']; printf("%d\n",size[p]); } }
(對於后綴數組,在下還不是很敏感,多做點之后再補充一些上來)
順便發兩張后綴自動機的圖
狀態 | 子串 | endpos |
---|---|---|
S | 空串 | {0,1,2,3,4,5,6} |
1 | a | {1,2,5} |
2 | aa | {2} |
3 | aab | {3} |
4 | aabb,abb,bb | {4} |
5 | b | {3,4,6} |
6 | aabba,abba,bba,ba | {5} |
7 | aabbab,abbab,bbab,bab | {6} |
8 | ab | {3,6} |
9 | aabbabd,abbabd,bbabd,babd,abd,bd,d | {7} |