[題解][筆記]AC自動機
阿新 • • 發佈:2020-10-26
[題解][筆記]AC自動機
演算法概述:
AC自動機的用途主要是在一個文字串中查詢多個短字串,它的主要構成就是一顆\(trie\)樹再加上一些\(kmp\)的思想.
模擬演算法過程:
首先給定\(4\)個模式串:\(ash\),\(shex\),\(bcd\),\(sha\).並建立一顆\(trie\)樹.
接著,我們之前說過\(ac\)自動機就是\(trie\)樹加\(kmp\)的思想,那麼\(kmp\)有\(border\)用來提升效率,\(ac\)自動機也有相類似的結構:\(fail\)指標.它的用處就是如果當前點失配了就直接把指標移動到\(fail\)指標所指的節點,就不需要回溯了.(\(fail\)
構造\(fail\)指標通常通過廣搜的辦法實現.對於根節點的兒子節點,它們的\(fail\)指標直接指向根節點,因為它們沒有後綴或字首.對於其他節點,如果有一樣的節點就直接相連,如果,沒有一樣的節點就連到根節點.
看程式碼吧,裡面有註釋
#include <bits/stdc++.h> using namespace std; char str[1000010]; struct node{ int fail;//失配指標 int cnt;//單詞出現的次數 int next[62];//兒子節點 }trie[1000010]; int k = 0,ans = 0; queue< int > q; void build_trie(int id,char s[]){ int len = strlen(s);//字串的長度,也是這個字串在trie樹中的深度(可以看上圖理解下) int j = 0; for(int i = 0;i < len;i++){//遍歷輸入的字串 j = s[i] - 'a'; if(trie[id].next[j]==0){//如果當前節點的兒子中還沒有這個字元就新建節點 trie[id].next[j]= ++k;//節點個數+1 } id = trie[id].next[j];//因為是建trie樹,所以建完的字串是一個從上到下的鏈,所以建完當前節點就建它的兒子節點 } trie[id].cnt++;//單詞數量+1 } void build_fail(int id){ while(!q.empty())q.pop(); for(int i = 0;i < 26;i++){//遍歷根節點的所有兒子節點 int j = trie[id].next[i]; if(j != 0){//如果存在這個兒子就入隊 q.push(j); trie[j].fail = id;//根節點的兒子的fail指標就是根節點 } } while(!q.empty()){//開始廣搜 int now = q.front(); q.pop(); for(int i = 0;i < 26;i++){//遍歷當前節點的兒子 int j = trie[now].next[i]; if(j == 0){//如果沒有這個兒子 trie[now].next[i] = trie[trie[now].fail].next[i];//它的兒子是它父親fail指標指向的節點的對應的兒子 //因為trie數是1~26代表a~z,所以next數組裡的第i個字母一定對應的是字母表中的第i個字元 //所以如果當前節點沒有"h"這個兒子,那麼在它指向的節點一定也是"h" continue; } trie[j].fail = trie[trie[now].fail].next[i];//當前節點的兒子的fail指標指向它父親fail指標指向的節點的對應的兒子 q.push(j); } } } void sovle(int id,char s[]){ int len = strlen(s),j = 0; for(int i=0;i<len;i++){ int j = trie[id].next[s[i] - 'a']; while(j && trie[j].cnt != -1){//如果存在這個節點並且這個節點有構成單詞 ans += trie[j].cnt;//答案累計合法單詞的數量 trie[j].cnt = -1;//不能重複累加 j = trie[j].fail;//直接指向它的失配指標 } id = trie[id].next[s[i] - 'a'];//往下找 } } int main(){ int n; scanf("%d",&n); for(int i = 1;i <= n;i++){ scanf("%s",str); build_trie(0,str); } build_fail(0); scanf("%s",str); sovle(0,str); printf("%d\n",ans); return 0; }