1. 程式人生 > 實用技巧 >AC自動機

AC自動機

AC來自一個大佬的名字,並不是寫了就可以自動AC的意思 XD

AC自動機是建立在trie樹上的一種優化手段。trie在每次查詢一個字串時,如果在一個子樹查不到就會回溯再查,效率會很低。我們考慮在給每個節點加一個如果查不到就跳轉的指標fail,那麼如果找不到的話直接跳轉到fail就可以了。fail代表的是擁有該點的最大字尾的點的位置。

那麼怎麼尋找這個fail呢?因為我們要尋找最大字尾,深度就是一個比較重要的條件。於是我們bfs。注意了,設一個點now,tmp為now的子節點(從a到z任何一個),有以下推導:

(第一段使用結構體儲存了點,第二段是用陣列)

e[tmp].fail=e[e[now].fail].nxt[i];
fail[tmp]=ch[fail[now]][i];

首先我們知道trie是有一個0節點的。那麼0連線的所有點的fail就連線的是0。如果我們接下來遍歷每個點,對於任何點的fail都是其父節點的fail的子字元。因為是bfs,所以這樣做一定是正確的而且一定會找到其最長字尾的點。

這裡注意一下:如果now點有該字元是按照上面記錄的。如果沒有(ch[now][i]==0)就需要轉移記錄一下nxt:

e[now].nxt[i]=e[e[now].fail].nxt[i];
ch[now][i]=ch[fail[now]][i];

這樣的話我們查詢的時候只需要令now不斷等於當前點的nxt就可以快速遍歷啦~如果遍歷到0,說明就end了。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define register R
using namespace std;
namespace ysr
{
    inline int read()
    {
        int w=0,x=0;char c=getchar();
        while(!isdigit(c))w|=c=='-',c=getchar();
        while
(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar(); return w?-x:x; } const int maxn=2333; int cnt=0; int n; char s[1000010]; struct tree{ int nxt[26],fail,cnt; bool vis; void init() { memset(nxt,0,sizeof nxt); fail=cnt=0; vis=0; } }e[maxn]; inline int get(char c) { return c-'a'; } inline void insert(char *s) { int now,tmp,t; for(now=0;*s;s++) { t=get(*s); if(!e[now].nxt[t]) { e[++cnt].init(); e[now].nxt[t]=cnt; } now=e[now].nxt[t]; } e[now].cnt++; } int ans=0; inline void match(char *s) { int now,tmp; for(now=0;*s;s++) { int t=get(*s); now=e[now].nxt[t]; for(tmp=now;tmp and !e[tmp].vis;tmp=e[tmp].fail){ ans+=e[tmp].cnt; e[tmp].vis=1; } } } inline void build() { int now=0; queue<int>q; while(!q.empty()) { int t=q.front();q.pop(); for(int i=0;i<26;i++) { if(e[now].nxt[i]){ int tmp=e[now].nxt[i]; if(now) e[tmp].fail=e[e[now].fail].nxt[i]; q.push(tmp); }else e[now].nxt[i]=e[e[now].fail].nxt[i]; } } } inline void work() { n=read(); e[0].init(); while(n--) { scanf("%s",s); insert(s); } scanf("%s",s); match(s); printf("%d\n",ans); } } int main() { ysr::work(); return 0; }