算法学习:AC自动机
【定義】
【自動機】??由 狀態集 ,初始狀態集 ,終止狀態集 ,字母集 ,對應關系五個元素組成的結構
? 可以簡單的將狀態集理解為結點,初始狀態集理解為初始點,終止狀態集理解為終點
? ? ? ? ? ? ? ? ? ?字母集理解為一個狀態能夠擁有的出邊的最大個數,而在自動機中,特殊的是,一個結點的所有出邊必須都要存在
? ? ? ? ? ? ? ? ? ?例如在AC自動機中,每個節點都必須要有26個字母的出邊所指向的節點
? ? ? ? ? ? ? ? ? ?對應關系,可以理解為連通的邊,例:節點 u 的 ’a‘ 的出邊能夠到達節點 v ,這就是一組對應關系
?
?注:自動機的概念不用知道也可以學自動機,但是個人感覺理解了自動機的含義,更容易去明白他的本質
? ? ? ? 而且后面學其他自動機也更容易理解
?
【模式串】? 一個比較短的串,需要找? 文本串上有多少個他
?
【文本串】? ?一個比較長的串,需要在? 他上面有多少個模式串
?
【前置知識】
【trie樹(字典樹)】從根節點插入一個字符串,依次插入
【KMP】單文本 logn 復雜度內查找模式串出現次數
?
【強烈建議看最后的擴展】?
?
【解決問題】
?多個模式串匹配文本串
給定一個較長串為文本串,給定多個模式串,詢問這兩者的關系
(也有可能不只有一個文本串)
一般為出現次數什么的
?
AC自動機本身保存這個字符串的所有子串的相關信息
即這個子串作為模式串出現的次數,這個子串的后綴之中能夠包含多少模式串
?
【算法思想】
這個自動機的優點和KMP類似,所以有的博客中也會說,這是一個樹上KMP。
KMP的優點在于有next數組作為指針失配時的指針,使字符串匹配可以不需要再次查找已經查找的串
而AC自動機也有他的“next數組”,f a i l??。
AC自動機中的 f a i l 指針表示的是,當當前匹配的模式串失效后,已經匹配的一半模式串的擁有最長后綴的模式串的結尾的位置
?
對于一個AC自動機,我們需要先把所有的模式串都插入一顆 trie 樹
int trie[MAXN][26]; //s是需要插入的模式串 void insert(string s) {int u = 0;//從根節點開始檢索for (int i = 0; i < s.size(); i++){int x = s[i] - 'a';if (!trie[u][x])//如果沒有這個節點//需要新開拓一個節點,保存這個新的分支 {trie[u][x] = ++cnt;}u = trie[u][x];}val[u]++;//說明這個節點是一個模式串的結尾return; }?
?然后根據我們對 f a i l 的定義去尋找f a i l 指針
?
void get_fail() {queue<int> q;for (int i = 0; i < 26; i++) if (trie[0][i]) fail[trie[0][i]] = 0, q.push(trie[0][i]);//讓所有的根節點連接的節點的fail指針指向根節點//并且加入隊列while (!q.empty()){int u = q.front(); q.pop();for (int i = 0; i < 26; i++)//循環查找每個字母 if (trie[u][i])//如果存在這個節點// {//他的 fail 就是 他父節點的 fail 的這個字母的位置//因為等于是在后綴上新加了一個字母 fail[trie[u][i]] = trie[fail[u]][i];q.push(trie[u][i]);}elsetrie[u][i] = trie[fail[u]][i];//如果沒有//向上遞歸一層,方便之后的查找 }return; }?
這里應該有張圖說明一下,但是我懶得畫
?
?
?
【模板題】
【題目大意】給n個模式串和1個文本串,問有多少個模式串在文本串中出現過
?
【解決方法】在trie樹上跑文本串
int query(string s) {int u = 0, ans = 0;//從根節點開始依次查找for (int i = 0; i < s.size(); i++){u = trie[u][s[i] - 'a'];//走到自己這個位置字符所在的節點for (int t = u; t && ~val[t]; t = fail[t])//從這個節點開始向上跳fail指針//查找有沒有符合要求的字符串,如果是則這個字符串就會被記錄ans += val[t], val[t] = -1;//-1是為了防止被重復計算 }return ans; }?
#include<cstdio> #include<iostream> #include<string> #include<queue> using namespace std; const int MAXN = 1000010; int fail[MAXN],cnt; int trie[MAXN][26]; int val[MAXN]; void insert(string s) {int u = 0;for (int i = 0; i < s.size(); i++){int x = s[i] - 'a';if (!trie[u][x]){trie[u][x] = ++cnt;}u = trie[u][x];}val[u]++;return; } void get_fail() {queue<int> q;for (int i = 0; i < 26; i++) if (trie[0][i]) fail[trie[0][i]] = 0, q.push(trie[0][i]);while (!q.empty()){int u = q.front(); q.pop();for (int i = 0; i < 26; i++)if (trie[u][i]){fail[trie[u][i]] = trie[fail[u]][i];q.push(trie[u][i]);}elsetrie[u][i] = trie[fail[u]][i];}return; } int query(string s) {int u = 0, ans = 0;for (int i = 0; i < s.size(); i++){u = trie[u][s[i] - 'a'];for (int t = u; t && ~val[t]; t = fail[t])ans += val[t], val[t] = -1;}return ans; } int main() {int T;cin >> T;while (T--){string s;cin >> s;insert(s);}get_fail();string s;cin >> s;printf("%d", query(s));return 0; } View Code?
【擴展】
?
這一步擴展其實才是AC自動機的精髓所在,也是比賽中比較常用的方法
就像是網絡流不可能直接給圖找最大流,而是通過建圖的方式考察對算法的理解
AC自動機也有同樣的情況
那就是通過抽離 fail 指針,建 fail 樹,并且對其進行一系列操作完成任務
?
?
這不就是AC自動機抽離fail指針建可持久化線段樹么
?
講解在另外一道題里面
【NOI 2011】 阿貍的打字機
?
轉載于:https://www.cnblogs.com/rentu/p/11297951.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的算法学习:AC自动机的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 算法学习:费用流
- 下一篇: 【P2766】 最长不下降子序列问题