[複習] AC自動機

dengchengyu發表於2024-10-24

[複習] AC自動機

自動機

從一個狀態透過接收一個訊號轉移到另一個狀態。
其實就是從一個點走一種顏色的邊到達另一個點,你會有一個初始點,然後每次走當前要走的顏色的邊,會走到一個目標點,目標點儲存著需要的答案。

AC自動機

\(Trie\) 為基礎,\(kmp\) 的字首函式思想構建的自動機。

用於解決多模式串匹配等任務。

例題:
給你一個文字串 \(S\),和 \(n\) 個模式串 \(T_i\),求每個模式串在文字串中出現了幾次。
\(|S|\le 2*10^5,n\le2*10^5,\sum T_i\le2*10^5\)

我們不能做 \(n\)\(kmp\),所以把它“合成一次做”。

考慮把每個模式串插入 \(Tire\) 上。

每個節點是一個字首,對於每個節點 \(S\) 維護這個 \(Trie\) 上最長的 \(S\) 的真字尾,即 \(border\),在這裡我們稱為失配指標 \(fail\)

\(tr[u][i]\) 表示 \(Trie\) 樹的樹邊,\(fail[u]\) 為失配指標。

  • 對於 \(u\) 沒有的邊 \(i\),我們給它重新連向 \(tr[fail[u]][i]\)

  • 而如果有這條出邊,那麼得到 \(tr[u][i]\)\(fail\)\(tr[fail[u]][i]\)

我們在 \(trie\)\(bfs\) 從而得到這兩個陣列,當原本就有 \(tr[u][i]\) 時,將 \(trie[u][i]\) 加入佇列。

for(int i=0;i<26;i++){
    if(trans[0][i])q.push(trans[0][i]);
}
while(!q.empty()){
    int u=q.front();q.pop();
    for(int i=0;i<26;i++){
        if(trans[u][i]){
            fail[trans[u][i]]=trans[fail[u]][i];
            q.push(trans[u][i]);
        }
        else trans[u][i]=trans[fail[u]][i];
    }
}

這裡有個細節就是,第一個字元的 \(fail\) 一定為 \(0\),所以要先遍歷根的兒子(就像字首函式要從 \(2\) 開始跑一樣)。

我們把 \(Trie\) 上沒有的邊都加上了,那現在 \(tr\) 陣列就是一個轉移函式,我們遍歷文字串,每新加一個字元就走一條邊,如此一來到達的節點就是當前文字串的字首最長的在 \(Trie\) 上的字尾。

我們從一個點一直跳 \(fail\),如果跳到了一個模式串的末尾,那麼說明這個模式串是當前文字串字首的字尾,即是文字串的子串。

我們不用每次跳 \(fail\),這樣時間複雜度是不正確的。

注意到 \(fail\) 是一棵樹,我們可以每次在當前點打上標記,到最後再遍歷樹統計。

相關文章