1. 程式人生 > >【模板】AC自動機

【模板】AC自動機

name display 功能 hide alt else AR namespace 存在

我:“woc。。。AC自動機?”

我:“可以自動AC???”

然鵝。。。

大佬:“傻。。。”

我:“(⊙_⊙)?”

大佬:“缺。。。”

我:“。。。。。。”

(大佬。。。卒 | 逃。。。)

emm。。。好吧。。。讓我們來看看AC自動機是啥。。。

寫在前面:如果沒有學過 KMP 和 Trie樹,請先理解這兩個算法。。。

這個東西因為是一個歪果仁發明,然後兩個單詞的首字母為AC,所以就被叫做AC自動機了。。。

這個東西簡單來說 AC自動機 == Trie + KMP

我們需要維護的東西也很簡單,類似於Trie樹上的KMP中的next數組。。。(作用類似,代表意義不同)

在AC自動機中有一個 fail 叫做失配指針。。。就是用它來進行跳轉進行匹配

這樣我們就可以發現。。。

Trie樹維護多個字符串的功能 + KMP判子串的功能 == AC自動機匹配多個字符串

emm。。。講解原理比較麻煩,直接看代碼和註釋比較好懂,當然建議根據大體的原理先瞎搞一波再看

呆碼:

技術分享圖片
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using
namespace std; struct asd{ int fail; // 失配指針 int vis[26]; // 子節點的位置 int end; // 標記有幾個單詞以這個節點結尾 } AC[1000010]; int rt,cnt,l,n; char ch[1000010]; inline void build(char *ch) { int l=strlen(ch+1); int now=rt,v; for(int i=1;i<=l;i++) { v=ch[i]-a; if(!AC[now].vis[v]) AC[now].vis[v]=++cnt; now
=AC[now].vis[v]; } AC[now].end+=1; } inline void get_fail() { queue <int> Q; for(int i=0;i<=25;i++) // 第一層肯定沒有之前的點與他匹配 if(AC[rt].vis[i]) Q.push(AC[rt].vis[i]); // 因為默認是 0 所以沒有寫指向根節點 while(!Q.empty()) { int u=Q.front(); Q.pop(); for(int i=0;i<=25;i++) // 枚舉所有子節點 { if(AC[u].vis[i]) // 存在這個子節點 { AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i]; // 子節點的fail指針指向當前節點的 // fail指針所指向的節點的相同子節點 Q.push(AC[u].vis[i]); } else //不存在這個子節點 AC[u].vis[i]=AC[AC[u].fail].vis[i]; // 當前節點的這個子節點指向當 // 前節點fail指針的這個子節點 } } } inline int query(char *ch) { int l=strlen(ch+1); int now=rt,ans=0,u; for(int i=1;i<=l;i++) { u=ch[i]-a; now=AC[now].vis[u]; for(int j=now; j && AC[j].end!=-1; j=AC[j].fail) { ans+=AC[j].end; AC[j].end=-1; } } return ans; } int main() { cin>>n; for(int i=1;i<=n;i++) { scanf("%s",ch+1); build(ch); } get_fail(); scanf("%s",ch+1); printf("%d\n",query(ch)); return 0; }
帶註釋

【模板】AC自動機