學習筆記第二十七節:AC自動機
阿新 • • 發佈:2018-11-09
正題
聽說NOIP要考,所以臨時補了一下,多了一種思考方式。
AC自動機是基於KMP和字典樹的,要想透徹瞭解AC自動機,最好先學KMP和字典樹。
那麼,AC自動機是什麼樣子的呢?又是用來解決什麼問題的呢?
【模板】AC自動機(簡單版),可以以這題為例。
好像KMP是可以做的,試一下,發現時間複雜度會爆炸,因為文字串要遍歷次。
通過磨題解,我們學習新演算法。
AC自動機。
首先我們把n個模式串插入一個字典樹中,接著,神奇的事情發生了。
我們對於字典樹中的每一個節點,我們找其對應的fail指標。
fail指標指的是什麼?從根到該節點的路徑很明顯構成一個字串。
那麼fail指標指向的就是這個字串,在字典樹中出現的最長的字尾。如圖。
是不是很醜,因為fail指標很好理解。
接著怎麼解決這一題呢?
先把AC自動機建出來,給每個字串的結尾所對應的節點權值+1.
然後拿文字串上去跳,讓它從根節點一直往下走(按照字元),如果一個節點沒有對應的字元,就不斷跳fail,直到跳到根。
如果當前節點是一個模式串的結尾,那麼ans+這個節點的權值,並且給這個節點打一個標記,表示記錄過了。(不計重複)
同時要加上fail指標的答案,因為滿足這個點一定滿足fail,fail指標是我的字尾啊。
#include<queue> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> using namespace std; int n; struct node{ int t,son[26],fail; bool tf; }s[1000010]; char ch[1000010]; int tot=0; queue<int> f; void insert(){ int l=strlen(ch+1); int x=0; for(int i=1;i<=l;i++){ int temp=ch[i]-'a'; if(s[x].son[temp]==-1){ s[x].son[temp]=++tot; s[tot].t=0;memset(s[tot].son,-1,sizeof(s[tot].son)); } x=s[x].son[temp]; } s[x].t++; } void get_fail(){ f.push(0); while(!f.empty()){ int x=f.front();f.pop(); for(int i=0;i<26;i++){ int y=s[x].son[i]; if(y==-1) continue; int now=s[x].fail; while(now!=0 && s[now].son[i]==-1) now=s[now].fail; if(x!=0 && s[now].son[i]!=-1) s[y].fail=s[now].son[i]; f.push(y); } } } int get_ans(){ int ans=0; int x=0,l=strlen(ch+1); for(int i=1;i<=l;i++){ int temp=ch[i]-'a'; while(x!=0 && s[x].son[temp]==-1) x=s[x].fail; if(s[x].son[temp]!=-1) x=s[x].son[temp]; int now=x; while(now!=0 && !s[now].tf) ans+=s[now].t,s[now].tf=true,now=s[now].fail; } return ans; } int main(){ scanf("%d",&n); memset(s[0].son,-1,sizeof(s[0].son)); for(int i=1;i<=n;i++) { scanf("%s",ch+1); insert(); } scanf("%s",ch+1); get_fail(); printf("%d",get_ans()); }