這是一個強大的automaton——Suffix Automaton==>我學過最強大,最牛犇,最難理解的自動機
現在給你一個問題:
給定一個字符串,要求這個字符串所有子串出現的次數分別是多少
樸素算法
①枚舉左端點,枚舉右端點,用hash記錄一下,統計個數。(注意最好雙hash,保證正確率)
預計時間複雜度:
②可以直接開trie記錄,以所有後綴建,就好像這樣:aabbabd
每次建的時候就記錄每個地方的次數,因爲有個點,那麼時間複雜度也就是
複雜度很優秀,但是…
n<=100000啊啊啊啊啊…
這樣做不了,怎麼辦?!
這時我們引進Suffix Automaton(後綴自動機)
(集中精神,前方高能)
後綴自動機定義
一個串的後綴自動機是一個有限狀態自動機,它能且只能接受所有的後綴(當然,它能做的事情遠遠不僅限於後綴).
後綴自動機其實是一個(有向無環圖),其中頂點是狀態,邊代表了狀態之間的轉移.
某一狀態被稱爲初始狀態,由它能夠到達其餘所有狀態.
自動機的所有轉移,都是一條有向邊,且都被某種符號標記,從某一狀態出發的所有轉移必須擁有不同的標記.
一個狀態被稱爲終止狀態,表示如果我們從初始狀態經任意一條路徑走到某一終止狀態,並順序寫出經過邊的標記,得到的字符串必定是原串的後綴.
在符合上述條件的所有自動機中,後綴自動機擁有最少的狀態與轉移,並且後綴自動機的狀態數以及轉移數都是的.
Pre的定義
可以說,pre是後綴自動機中最核心的東西,也是最難懂的部分。所以認真看!
pre[x]表示字符串[Sx]的最長後綴[Spre[x]]的右端點,例如:
綠色的虛線表示pre邊。很顯然我們可以發現3的pre邊連向的是[S3]的最長後綴[S1]的右端點1。
Pre的連邊
對於剛剛加入的now點,尋找las的pre邊,如果沒有一條與當前新加的這條邊相同的邊就加上一條這樣的邊,直到找到一個有與當前新加的這條邊相同的邊爲止。然後分爲兩種情況:我們設當前找到的這個點爲p,它連向的兒子爲q。
①如果p,q兩點距離爲1,那麼就可以直接把當前新節點的pre邊指向q。
②如果p,q兩點距離大於1,我們會發現如果把當前新節點的pre邊指向q,不符合[Sq]爲[Snow]的後綴(如下圖)
(ab並不是abb的後綴)
我們分析這樣做錯誤的原因,因爲我們把原串中的ab當成了b(就是連在下面的那條邊),所以把ab誤認爲成了abb的後綴,那麼我們想,我們呢原來是想連b爲後綴的,所以這是我們需要進行加點操作。
加點操作
我們在p以後加入一個點,與p及以前所有與q用當前符號爲邊相連的點,與新加入的點連上一條與當前符號相同的邊,例如上圖是b,因爲這個點在原串中是不存在的,它只是q的一個分身,所以q指出去的所有邊它也都要連,然後新加的點的pre邊自然就變成了q原來的pre邊,然後q和now的pre邊也就指向了當前新加節點:
對於“aabbabd”來說它的自動機長這樣:
時間複雜度
時間複雜度是:.
最大的狀態數是2n-1,因爲除了前面三個點,後面的所有點都可以加點。
#應用
1.給定字符串T,每次詢問一個p,問p是否爲T的子串.
- 對T建一個後綴自動機.
- 每次詢問就從初始狀態ss開始走,然後沿着詢問的字符串走.
- 時間複雜度
2.給定字符串S,問它有多少不同的子串.
- 還是先建一個後綴自動機.
- 那麼對於後綴自動機中的任何一條路徑,都是一個不同的子串.
- 所以答案就是從S開始出發的不同路徑數.
3.給定字符串S,每次詢問S的所有不同子串中字典序第k小.
- 和前兩問類似,我們只需要處理從一個狀態開始,每種字符的路徑條數.
- 然後從起點S一直找就好了.
4.給定字符串S,找到和它循環同構的字典序最小的字符串.
- 我們對字符串S+S做一個後綴自動機,然後貪心的找字典序最小就好啦~
5.給定多個字符串,求它們的最長公共子串.
- 思考…
模板Code
#include<cstdio>
#include<iostream>
#include<cstring>
#define maxN 2000010
using namespace std;
int son[maxN][27],pre[maxN],len[maxN],sum[maxN];
int sz,las,lens,now,q,p;
char s[maxN];
void add(int x)
{
len[++sz]=len[las]+1,sum[sz]=1,now=sz;
for (p=las;p&&!son[p][x];p=pre[p]) son[p][x]=now;
if(p)
{
q=son[p][x];
if(len[q]>1+len[p])
{
len[++sz]=len[p]+1;
memcpy(son[sz],son[q],sizeof(son[q]));
pre[sz]=pre[q];pre[q]=pre[now]=sz;
for (;son[p][x]==q;p=pre[p]) son[p][x]=sz;
}
else pre[now]=q;
}
else pre[now]=1;
las=now;
}
int main()
{
scanf("%s",s+1);
lens=strlen(s+1);sz=las=1;
for (int i=1;i<=lens;++i) add(s[i]-96);
}