日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

后缀自动机:从入门到放弃

發布時間:2023/12/3 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 后缀自动机:从入门到放弃 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

寫在前面

后綴自動機,簡稱SAMSAMSAM,是一種十分優秀的字符串匹(shu)配(ju)算(jie)法(gou)

字符串界的bossbossboss,幾乎可以解決全部正常的字符串題目

至少我前前后后學了一年,聽過444次課,幾度懷疑自己不適合oioioi

請做好心理準備

定義

有限狀態自動機

不管,可以理解為有向圖。

唯一的區別是信息儲存在邊上,每個點有字符集個數的轉移到若干其他點,類比字典樹。

如果對于一個字符串,沿著每個點的轉移走,如果不出界,稱該自動機可以表示這個字符串。

以下將“點”稱為狀態

能干啥

后綴自動機能表示母串的所有子串。

算法流程

首先明確一點:子串規模是O(N2)O(N^2)O(N2)

所以一個狀態必須表示多個子串

也就是說,我們要定義出等價的子串

對于一個子串SSS,定義endpos(S)endpos(S)endpos(S)SSS在原串中所有出現的結束位置的集合

每個狀態與一個endposendposendpos集合一一對應。即一個狀態表示一個endposendposendpos集合。

需要注意的是,endposendposendpos是完全虛構的,在代碼中不會出現。

然后可以表示所有endposendposendpos等于它的字符串,稱這些子串為一個endposendposendpos等價類

轉移

此時我們定義轉移為這個類所有串加上這個字符后所在的轉移。

一個類的同一個轉移是相同的,因為向ccc的轉移的本質是當前endposendposendpos整體后移一位的所有ccc的位置。

感性理解。

兩個性質

1.兩個子串S1,S2S_1,S_2S1?,S2?滿足S1S_1S1?S2S_2S2?的后綴,當且僅當endpos(S2)?endpos(S1)endpos(S_2)\subseteq endpos(S_1)endpos(S2?)?endpos(S1?)

S2S_2S2?出現的地方一定有S1S_1S1?出現,但有S1S_1S1?出現的地方不一定有S2S_2S2?

2.一個等價類中的子串均為該類中最長串的后綴且長度連續

第一個顯然

對于一個串SSS,若有后綴S1S_1S1?長度小于SSS,且S1S_1S1?SSS是等價類

S2S_2S2?為長度在它們之間的后綴

endpos(S)?endpos(S2)?endpos(S1)endpos(S) \subseteq endpos(S_2) \subseteq endpos(S_1)endpos(S)?endpos(S2?)?endpos(S1?)

因為endpos(S)=endpos(S1)endpos(S)=endpos(S_1)endpos(S)=endpos(S1?)

所以endpos(S)=endpos(S2)=endpos(S1)endpos(S) = endpos(S_2) = endpos(S_1)endpos(S)=endpos(S2?)=endpos(S1?)

說明它們之間的串都是一個等價類

Parent鏈

由于類中的長度只有一段,逼死強迫癥

所以我們定義每個狀態SSSfailfailfail指針

滿足endpos(S)∈endpos(fail(S))endpos(S) \in endpos(fail(S))endpos(S)endpos(fail(S))

fail(S)fail(S)fail(S)要盡量靠后

可以理解為:從一個狀態沿failfailfail往上跳,取出該類中的所有串,你將會見證這個串不斷失去第一個字符,不斷變為后綴,最后變成空串。我們稱這條鏈為parentparentparent鏈。

先放個圖,以AABAABAAB為栗子

可能看不出啥,但有個大概印象吧

構造算法

SAM 采用增量算法,即一個一個字符插入

這使得 SAM 擅長處理動態問題

現在假設插入第iii個字符,前i?1i-1i?1個的 SAM 已經建立好

首先,上一個插入的點是整個串所在的狀態,記為ppp

新建一個節點,記為curcurcur。顯然curcurcur最長的長度為當前串的長度。

由于其他子串已經處理了,我們要做的,就是搞出當前串的后綴

此處分333種情況

①最簡單的情況

栗子:AA\texttt {AA}AA插入B\texttt BB

此時curcurcur{3}\{3\}{3}

由于每個類里的字符串是等價的(感性理解)

我們可以找到舊的串的所有后綴,給它加上新的字符

也就是讓ppp沿著failfailfail不斷跳,令ch[p][S[i]]=curch[p][S[i]]=curch[p][S[i]]=cur

即:原來的所有后綴加上新來的字符就成了新的后綴


最后fail(cur)=1fail(cur)=1fail(cur)=1,完結撒花


②然而這只是最簡單的情況

栗子:AA\texttt {AA}AA插入A\texttt AA

沒區別


咦?已經有轉移了呀

說明什么?

說明現在新串的這個后綴已經在之前的串中出現了

那這個后綴的后綴也一定出現了

(請擺脫這個栗子)

q=ch[p][S[i]]q=ch[p][S[i]]q=ch[p][S[i]]

上面的話翻譯一下,ppp表示的串+S[i]+S[i]+S[i]已經出現了

而這個玩意就是qqq

……嗎?

ppp的最長串+S[i]+S[i]+S[i]一定在qqq上(定義),但不一定是qqq最長的

先討論是最長的的情況

yyyyyy一下,我們要找的后綴不就是qqq的最長串嗎?

而這個后綴的后綴,也就是我們后面要找的,就在qqqparentparentparent鏈上

這么說來,我們令fail[cur]=qfail[cur]=qfail[cur]=q就好了


over

③然而還有個最復雜的情況

也就是上面的不是qqq最長的

栗子:AAB\texttt{AAB}AABB\texttt BB


走程序


此時的qqqB,AB,AAB\texttt {B,AB,AAB}B,AB,AAB

而我們的curcurcur只想要B\texttt BB和鏈上的東西

怎么辦?

拆了唄


記新建的點為q′q'q

維護信息

首先curcurcur要的是q′q'qqqq祖先上的

然后我們發現qqqq′q'q有后綴關系

接下來維護轉移

q和q′q和q'qq是同一個分出來的,而它們原來的轉移共同構成了后面的狀態

現在它們拆開了,理應維護好后面

即:將qqq的轉移拷貝給q′q'q

考慮這樣的事實:在前面某個位置,原來轉移到了這個狀態?,F在這個狀態分了,需要考慮具體轉移到哪一邊。

注意到轉移到q′q'q而不轉移到qqq,當且僅當這個狀態最長串長小于q′q'q最長串長。

并且都是qqq去掉末尾的一個字符后的后綴

根據意識流這樣的狀態最后面的滿足的剛好是ppp

而剩下的都在pppparentparentparent鏈上

即:跳pppparentparentparent鏈,如果有到qqq的轉移,將它改到q′q'q

因為是后綴,所以一定是S[i]S[i]S[i]的轉移(因為ch[p][S[i]]=qch[p][S[i]]=qch[p][S[i]]=q

至此,SAM 就構造完畢了

復雜度是O(N)O(N)O(N)的,顯然我不會證

代碼

具體實現的時候,每個節點只記錄最長串的長度lenilen_ileni?

int fa[MAXN],ch[MAXN][26]; int len[MAXN],last=1,tot=1; int siz[MAXN],a[MAXN],c[MAXN]; void insert(int c) {int p=last,cur=++tot;len[cur]=len[last]+1;last=cur;for (;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;//跳failif (!p) fa[cur]=1;//情況1else{int q=ch[p][c];if (len[p]+1==len[q]) fa[cur]=q;//情況2else//情況3{int clone=++tot;len[clone]=len[p]+1;for (int i=0;i<26;i++) ch[clone][i]=ch[q][i];fa[clone]=fa[q];fa[q]=fa[cur]=clone;for (;ch[p][c]==q;p=fa[p]) ch[p][c]=clone;} } }

運用

劈配子串

按照定義,沿著轉移走

最長公共子串

建出SSS的后綴自動機,拿TTT去跑

不斷用lenilen_ileni?更新答案

如果走不動了就跳failfailfail

處理出現次數

對于每一次插入,一個類出現次數增加,當且僅當這是當前的后綴

也就是把這個點的parentparentparent鏈都+1+1+1

顯然會TTT。于是先建完,按長度瞎排個序,倒著往上推。

這樣sizisiz_isizi?表示狀態iii中的一個串的出現次數。顯然它們是一樣的。

應該是用的最多的。

void query() {for (int i=1;i<=tot;i++) c[len[i]]++;for (int i=1;i<=n;i++) c[i]+=c[i-1];for (int i=1;i<=tot;i++) a[c[len[i]]--]=i;for (int i=tot;i>=1;i--) siz[fa[p]]+=siz[p]; }

然后你就可以處理各種沙雕的字符串題

本質不同的串的個數

由于一個串只會被表示一遍

我們相當于求所有狀態表示的串的個數之和。

∑(len[p]?len[fa[p]])\sum( len[p]-len[fa[p]])(len[p]?len[fa[p]])

Link Cut Tree維護parent鏈

先寫到這里吧,想到再補。

總結

以上是生活随笔為你收集整理的后缀自动机:从入门到放弃的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。