字尾自動機相關

2022-02-27 05:18:59 字數 3807 閱讀 9701

本篇部落格主要講一些自己的理解,不喜勿噴

發現網上的部落格關於\(right\)集合只講了如何做,沒有講清原因,於是和同學討論了很久之後記錄一下

如有沒有看懂的地方或者錯誤的地方,歡迎提問或者指出

如有其它疑問也可提出,博主也會進行解答

本部落格內容為對各種方法的理解與深入分析,所以有點部分會長了一點點

若只想知道方法,可跳過中間的思考

\(parent\)樹上的節點與字尾自動機的節點完全一樣,但是邊不一樣

根節點出發,能走出所有的子串,並只能走出子串

\(endpos\)是乙個集合,表示乙個串出現的所有位置,以最後乙個字元所在位置為出現位置

\(right\)集合表示\(endpos\)相同的子串的集合,乙個\(right\)集合可以表示多個子串,這些子串的長度一定是連續的,並且有字尾關係,\(right\)的大小表示的就是其所表示的\(endpos\)集合的大小,即該\(endpos\)集合有多少元素

兩個\(right\)的關係只有兩種,要麼是包含關係,要麼互相獨立,不會有交集

若兩個串有相同的\(endpos\),那麼必有乙個為另乙個的子串

乙個\(right\)集合\(r1\)可以被另乙個\(right\)集合\(r2\)包含,此時一定滿足\(r2\)是\(r1\)的字尾

根據2.,我們可以在字尾自動機上從根節點開始跑,可以跑出所有子串,跑到某個點時,此時該點的\(right\)的大小,亦表示當前匹配到的這個字串出現次數

\(parent\)樹可由字尾自動機建出來,每個節點的\(fail\)節點所表示出的字串一定是其字尾,因為\(fail\)節點的\(right\)集合包含該節點。

為什麼?因為字尾自動機就是要建出這樣的樹 怎麼建出來可去看其它dalao的詳細講解,本文只會講關鍵意義

自動機節點的字尾表示的是以該節點為末尾的字尾

事先申明,本人打的是陣列版

\(\begin

&fa \rightarrow fail \\

&len \rightarrow longest\ length \\

&size \rightarrow size\ of\ right

\end\)

主串為原串的字首,舊主串為上乙個字首加入自動機後是哪個節點

另外,\(size\)不影響構建字尾自動機,可以忽視,本文會在後面講\(parent\)時講\(size\)

#include #include const int maxn = 2000006;

const int maxc = 27;

int tot=1,last=1;//last -> 舊主串的節點

int fa[maxn],len[maxn],size[maxn];

//fa -> fail fa[x]的right集合一定包含x fa[x]一定是x的字尾

//len[x] -> x為字尾最長串長度

//size[x] -> x 號節點表示的right集合的大小

int son[maxn][maxc];//son[p][c] -> 在p所代表的集合後加c字元,該字元c是哪個節點 亦可認為是邊

//1 號節點為初始節點 初始節點沒有fa

// }

size[np]=1;//該節點right大小初值賦值為1

}//}}}

我們知道,\(right\)在\(parent\)樹上是有各種包含關係

所以要在\(parent\)上求\(right\)集合大小

先將主串的\(right\)集合的大小賦初值為1,該過程在構建字尾自動機時完成

乙個\(right\)集合的大小即為其兒子\(right\)集合的大小的和加上其原本的大小(可能為1)

\(right\)集合沒有交集,主串節點的\(right\)集合的大小就是1

只有主串表示的點能是葉子節點,但是葉子節點的\(right\)集合不僅僅表示主串上的點,且主串上的點不一定是葉子節點

為什麼兩種理解

所以給主串賦初值為1是正確且必須的

所以我們要做的就是,先建出\(parent\)樹,然後在上面跑一次\(dfs\)

當然,我們也可以直接遞推,因為是\(dag\),所以可以拓撲的去計算,即由兒子算父親,長度越大自然在\(parent\)樹上就處於越下面的位置,所以按照長度從大到小排序,這一步可以選擇\(sort\),也可以基數排序

\(\mathcal\)

for (int i=2;i<=tot;++i)	add(fa[i],i);//因為根節點沒有fa,所以要從2開始列舉,當然也可以設根節點為0,那麼還得給根節點的fa賦初值為-1,上面特判也得改一下

dfs(1);

void dfs (int p)

}//基數排序 常數較小

for (int i=1;i<=tot;++i) ++cup[len[i]];

for (int i=1;i<=n;++i) cup[i]+=cup[i-1];

for (int i=1;i<=tot;++i) mp[cup[len[i]]--]=i;

for (int i=1;i<=tot;++i) size[fa[i]]+=size[i];

\(right\)集合個人認為很神奇

我們會發現,\(right\)集合的大小僅由主串節點更新,遇到主串節點時其\(size\)就會加1

換句話說,乙個節點\(right\)集合的大小等於其子樹(包括它自己)中,有多少個節點是主串節點

這句話該怎麼理解

那麼乙個節點的\(right\)集合大小等於該串是多少個字首的字尾

所以,在\(parent\)樹上乙個節點的\(right\)集合的大小亦可表示為

\(endpos\)為該\(right\)集合所對應的\(endpos\)集合的串是多少個字首的字尾

亦可表示該子樹中有多少個主串節點

根據2.

根節點出發,能走出所有的子串,並只能走出子串

在字尾自動機上從根節點出發跑一遍即可

trie樹又被吊打了

這個有兩種方式

若該不同子串有這樣的定義

位置不同的相同子串算不同子串

我們就可考慮7.

根據2.,我們可以在字尾自動機上從根節點開始跑,可以跑出所有子串,跑到某個點時,此時該點的\(right\)的大小,亦表示當前匹配到的這個字串出現次數

先求出每個\(right\)的大小,每遇到乙個結點給答案加\(size\)即可

把從該節點出發還有多少子串記錄下來,然後向\(splay\)那樣跑即可

ll dfs (int x)//先處理出還有多少子串

void kth (int x,ll k)//求第k小

else k-=num[son[x][i]];

} }}

求第\(k\)大只需把迴圈改為從\(26\)向\(1\)列舉即可

用最小表示法既簡單**又短,用什麼字尾自動機

我們將\(s+s\)的字尾自動機建出

然後就是要找乙個最小的長度為\(|s|\)的子串了

同上方**...

就講這麼多吧

這麼辛苦寫了好幾天(寫了一半發現有新問題於是又想了兩天)

給個贊吧

字尾自動機

基礎知識 step i 表示的是字串i在原字串中的位置。pareint i 表示root到parent i 的子串是root到i的最長字尾。字尾自動機遍歷可以得到原字串的所有子串。特殊技巧 一 字尾自動機的不同子串數有兩種求法 1.ans step i step parent i 1 i cnt 2...

字尾自動機

常用於處理字串問題,可以高效解決許多字串問題。有點像將乙個字串的所有字尾都建在乙個ac自動機上,但不同的是字尾自動機的節點數最多為2 n,因為它只記錄需要記錄的點,一些沒有記錄東西的點可以視為與下面有價值的節點並在一起,這樣大大降低了時間複雜度和空間複雜度。對於每乙個節點記錄它的後面加上每個字元後字...

字尾自動機

基礎學習 簡潔明瞭的講解 總狀態數不超過2n 12n 1 2n 1 包括初始狀態 統計每個end po sendpos endpos 等價類出現位置數量時,要按長度從長到短的計算cnt cntcn t。那為什麼一定要從長到短呢?比如回文自動機就直接是按照節點編號從大到小計算cnt cntcn t 罪...