AC自動機---好東西
阿新 • • 發佈:2020-12-31
一.前言
二.正文
AC自動機到底是什麼呢?\(Trie+KMP\)
我們的AC自動機和KMP有什麼區別呢?
AC自動機可以同時匹配很多個模式串,而KMP只可以匹配一個模式串。
那麼我們來看一道例題:傳送門
我們可以想到對於每一個字串進行一次KMP,然鵝會TLE。
AC自動機就是一個演算法,它可以同時進行多次KMP。
我們舉一個例子:
我們可以驚奇的發現:
這裡\(she\)的字尾\(he\),和\(he\)是一樣的,這時候我們想到了什麼?
我們可以建立一個\(fail_{cur}\)的陣列,在\(she\)後的\(e\)建立一個指向\(he\)
我們就可以很方便的進行匹配了。
現在我們的關鍵就在於如何求出\(fail\)。
假設我們有一點\(trie[cur][i]\),它的父親\(cur\),若\(cur\)的\(fail\)已經求出,那麼只會有兩種情況
- 我們的\(trie[cur][i]\)不為空,那麼就直接把\(fail[trie[cur][i]]=fail[cur][i]\),意思就是在自己的\(fail_{cur}\)下尋找一個\(i\)代表的陣列。
- 若\(trie[cur][i]\)為空,直接把這個點\(trie[cur][i]=trie[fail[cur]][i]\)
這樣就好了。
我們查詢的時候就一個字元一個字元的找,每一次針對一個字元,不停地根據\(fail\)
在這個過程中,我們要注意統計一次答案,就要把\(Trie\)中的計數器\(cnt\)清空為\(-1\),代表跳到這裡就跳不了了。
我們就可以輕鬆的打出程式碼
Code:
#include<iostream> #include<queue> using namespace std; const int N = 1000005; int trie[N][28], tot, num[N], fail[N]; queue<int> q; void insert(string s) { int cur = 0; for(int i = 0; i < s.size(); i++) { if(trie[cur][s[i]-'a'] == 0) { tot++; trie[cur][s[i]-'a'] = tot; } cur = trie[cur][s[i]-'a']; } num[cur]++; //統計以cur結尾的單詞出現的次數 return ; } void get_fail() //bfs構建fail陣列 { for(int i = 0; i < 26; i++) //根結點下面直接連的第一層結點,fail直接指向根結點0 if(trie[0][i]) q.push(trie[0][i]); while(q.empty() == false) //佇列中維護能夠拓展fail值的結點 { int cur = q.front(); q.pop(); for(int i = 0; i < 26; i++) { if(trie[cur][i]) { //失配時,以trie[u][i]結尾的字尾儘量在trie中找一個與之相同的字首(類似KMP) fail[trie[cur][i]] = trie[fail[cur]][i]; q.push(trie[cur][i]); } else //節點不存在,往上連,最多回到根結點0, 注意是trie不是fail陣列 trie[cur][i] = trie[fail[cur]][i]; } } return ; } int query(string t) //詢問,t是文字串 { int cur = 0, res = 0; //cur表示trie中的結點 for(int i = 0; i < t.size(); i++) { cur = trie[cur][t[i] - 'a']; //獲取t[i]所對應的結點 for(int j = cur; j && num[j] != -1; j = fail[j]) { res += num[j]; num[j] = -1; //標記為統計過 } } return res; } int main() { int n; string s; cin >> n; for(int i = 1; i <= n; i++) { cin >> s; insert(s); } cin >> s; //文字串 get_fail(); cout << query(s); return 0; }