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