模板:后缀自动机(SAM)
所謂后綴自動(dòng)機(jī),就是通過后綴建立的自動(dòng)機(jī)
(逃)
請(qǐng)?jiān)试S我先介紹一下后綴家族:
(又逃)
前言
OI生涯目前為止學(xué)習(xí)的最為難以理解的算法,沒有之一。
到現(xiàn)在也沒有完全的理解。
qwq
概念
定義:
后綴 iii :字符串 sss 以 iii 結(jié)尾的后綴(前綴同理)
endpos(x)endpos(x)endpos(x) 字符串 xxx 在 sss 中出現(xiàn)的結(jié)尾位置的集合
等價(jià)類:若 endpos(u)=endpos(v)endpos(u)=endpos(v)endpos(u)=endpos(v),我們就稱 uuu 和 vvv 屬于同一個(gè)等價(jià)類
不難發(fā)現(xiàn),對(duì)于 sss 的一個(gè)子串 x=sl...rx=s_{l...r}x=sl...r?,都存在一個(gè)位置 p∈(l,r]p\in (l,r]p∈(l,r],滿足對(duì)于 l≤i≤pl\le i\le pl≤i≤p,xxx 的后綴 iii 都與 xxx 屬于同一個(gè)等價(jià)類,而對(duì)于 p<i≤rp<i\le rp<i≤r,后綴 iii 與 xxx 都不屬于一個(gè)等價(jià)類。
我們稱滿足這個(gè)性質(zhì)的 ppp 為 link(x)link(x)link(x)。
SAM是一個(gè)字符轉(zhuǎn)移邊組成的DAG,每條從根結(jié)點(diǎn)出發(fā)的路徑都唯一對(duì)應(yīng) sss 的一個(gè)子串,在SAM中,每個(gè)結(jié)點(diǎn)都對(duì)應(yīng)著一個(gè)等價(jià)類的集合(也就是從根結(jié)點(diǎn)到該結(jié)點(diǎn)的路徑所代表的字符串的集合,這些字符串必然由一個(gè)最長的字符串和它的一些連續(xù)的后綴組成),一個(gè)結(jié)點(diǎn)的 linklinklink 定義為該結(jié)點(diǎn)對(duì)應(yīng)的最長字符串的 linklinklink。
請(qǐng)務(wù)必確保你理解了上面這段話
構(gòu)建
現(xiàn)在考慮如何構(gòu)建出SAM
使用增量法,當(dāng)前加入一個(gè)新的字符串 ccc
設(shè)上一個(gè)加入的結(jié)點(diǎn)為 lstlstlst,當(dāng)前加入結(jié)點(diǎn)為 curcurcur
設(shè)一個(gè)結(jié)點(diǎn)代表的最長字符串的長度為 lenlenlen
首先,令 len(cur)←len(lst)+1len(cur)\gets len(lst)+1len(cur)←len(lst)+1
然后從 lstlstlst 沿著 linklinklink 不斷往上跳,直到跳到某個(gè)有 ccc 的轉(zhuǎn)移邊或者跳到根為止,沿途把 ccc 的轉(zhuǎn)移邊全部賦值成 curcurcur
situation 1
若到根了還沒有 ccc 的轉(zhuǎn)移邊:說明整個(gè)字符串還沒有出現(xiàn)過 ccc,直接把 link(cur)link(cur)link(cur) 賦值成根即可
否則,設(shè)跳到了結(jié)點(diǎn) ppp,ppp 的 ccc 轉(zhuǎn)移邊為 qqq
由于一直跳的是
situation 2
若 len(q)=len(p)+1len(q)=len(p)+1len(q)=len(p)+1:這兩個(gè)結(jié)點(diǎn)在原串上就是相鄰的,直接令 link(cur)=link(q)link(cur)=link(q)link(cur)=link(q) 即可
situation 3
若 len(q)≠len(p)+1len(q)\ne len(p)+1len(q)?=len(p)+1:這兩個(gè)結(jié)點(diǎn)在原串上不是相鄰的,此時(shí)若按照情況2的處理方法,會(huì)使SAM上出現(xiàn)不應(yīng)該出現(xiàn)的前綴,所以我們應(yīng)該分裂出一個(gè)結(jié)點(diǎn) pppppp,繼承所有 qqq 的信息,len(pp)←len(p)+1len(pp)\gets len(p)+1len(pp)←len(p)+1,并把 qqq 和 curcurcur 的 linklinklink 全指向 pppppp,再一路往上把本來連向 qqq 的轉(zhuǎn)移連向 pppppp
代碼
void ins(int c){c-='a';int cur=++tot,p=lst;lst=tot;st[cur].len=st[p].len+1;siz[cur]=1;for(;p&&!st[p].tr[c];p=st[p].fa) st[p].tr[c]=cur;if(!st[p].tr[c]) st[cur].fa=1;else{int q=st[p].tr[c];if(st[q].len==st[p].len+1) st[cur].fa=q;else{int pp=++tot;st[pp]=st[q];st[pp].len=st[p].len+1;st[q].fa=st[cur].fa=pp;for(;p&&st[p].tr[c]==q;p=st[p].fa) st[p].tr[c]=pp;return;}} }應(yīng)用
求 endpos 集合大小
定義 sizxsiz_xsizx? 為結(jié)點(diǎn) xxx 的等價(jià)類集合中 endposendposendpos 的數(shù)目。(也就是出現(xiàn)次數(shù))
那么根據(jù)定義,有:
sizx=∑s∈sonxsizs+[x∈S]siz_x=\sum_{s\in son_x} siz_s+[x\in S]sizx?=s∈sonx?∑?sizs?+[x∈S]
其中 SSS 是每次插入的終點(diǎn)集合
dfs或者拓?fù)鋵?shí)現(xiàn)均可
求本質(zhì)不同子串?dāng)?shù)
就是在自動(dòng)機(jī)上的走法種類唄。
那么就有:
sumx=∑s=trx,csums+1sum_x=\sum_{s=tr_{x,c}}sum_s+1sumx?=s=trx,c?∑?sums?+1
Thanks for reading!
后綴樹
本身也是一個(gè)大算法,但是可以通過反串建SAM偷懶。
復(fù)雜度為 O(nC)O(nC)O(nC)。
解析
對(duì)反串建出后綴自動(dòng)機(jī),其 failfailfail 樹即為所求的后綴樹。
設(shè) plxpl_xplx?,為 xxx 節(jié)點(diǎn)對(duì)應(yīng)的任意一個(gè)出現(xiàn)位置,那么 failx→xfail_x\to xfailx?→x 這條邊上對(duì)應(yīng)的字符串就是 s(plx+lenfailx,plx+lenx?1)s(pl_x+len_{fail_x},pl_x+len_x-1)s(plx?+lenfailx??,plx?+lenx??1)。
結(jié)合 failfailfail 的定義應(yīng)該不難得到。
后綴樹的性質(zhì):
在解決字典序相關(guān)問題時(shí)較為常用。
總結(jié)
以上是生活随笔為你收集整理的模板:后缀自动机(SAM)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 成也韩援败也韩援 LNG世界赛打出了一场
- 下一篇: 第二代The Freestyle三星随享