1. 程式人生 > 實用技巧 >AC自動機學習筆記

AC自動機學習筆記

原部落格地址:https://www.cnblogs.com/hyfhaha/p/10802604.html

問題的背景

來一道題:

給定n個模式串和1個文字串,求有多少個模式串在文字串裡出現過。

注意:是出現過,就是出現多次只算一次。

我們將n個模式串建成一顆字典樹。但是當我們匹配成功一個模式串後,需要重新回到根做匹配,這樣效率太低了。

比如這個字典樹,我們從4號點匹配失敗後,可以繼續從7號點開始匹配,然後匹配到8。

那麼我們怎麼確定從哪個點開始匹配呢?我們稱i匹配失敗後從j開始匹配,j是i的失配指標。

失配指標的實質含義是什麼?

如果一個點的Fail指標指向j,那麼root到j的字串是root到i的字串的一個字尾。

所以Fail指標的含義是:最長的當前字串的字尾在字典樹上可以找到的末尾編號。

求Fail指標

首先我們可以確定,每一個點i的Fail指標指向的點的深度一定是比i小的。

第一層的Fail一定指的是root。

設點i的父親fa的Fail指標指的是Fafail,那麼如果Fafail有和i值相同的兒子j,那麼i的Fail就指向j。(此時我已經懵逼)

具體實現:

void getFail () {
    for (int i=0;i<26;i++) Trie[0].son[i]=1;
    queue<int> q;
    q.push(1);
    Trie[1].fail=0
; while (!q.empty()) { int u=q.front(); q.pop(); for (int i=0;i<26;i++) { int v=Trie[u].son[i]; int Fail=Trie[u].fail; if (!v) { Trie[u].son[i]=Trie[Fail].son[i]; continue; } Trie[v].fail
=Trie[Fail].son[i]; q.push(v); } } }

查詢

int query (char * s) {
    int u=1;
    int ans=0;
    int len=strlen(s);
    for (int i=0;i<len;i++) {
        int v=s[i]-'a';
        int k=Trie[u].son[v];
        while (k>1&&Trie[k].f!=-1) {
            ans+=Trie[k].f;
            Trie[k].f=-1;
            k=Trie[k].fail;
        }
        u=Trie[u].son[v];
    }
    return ans;
}

完整程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+100;
struct trie {
    int son[26];
    int f;
    int fail;
}Trie[maxn];
int n,cnt;
char s[maxn];
queue<int> q;
void insert (char * s) {
    int u=1;
    int len=strlen(s);
    for (int i=0;i<len;i++) {
        int v=s[i]-'a';
        if (!Trie[u].son[v]) Trie[u].son[v]=++cnt;
        u=Trie[u].son[v];
    }
    Trie[u].f++;
} 
void getFail () {
    for (int i=0;i<26;i++) Trie[0].son[i]=1;
    queue<int> q;
    q.push(1);
    Trie[1].fail=0;
    while (!q.empty()) {
        int u=q.front();
        q.pop();
        for (int i=0;i<26;i++) {
            int v=Trie[u].son[i];
            int Fail=Trie[u].fail;
            if (!v) {
                Trie[u].son[i]=Trie[Fail].son[i];
                continue;
            }
            Trie[v].fail=Trie[Fail].son[i];
            q.push(v);
        }
    }
}
int query (char * s) {
    int u=1;
    int ans=0;
    int len=strlen(s);
    for (int i=0;i<len;i++) {
        int v=s[i]-'a';
        int k=Trie[u].son[v];
        while (k>1&&Trie[k].f!=-1) {
            ans+=Trie[k].f;
            Trie[k].f=-1;
            k=Trie[k].fail;
        }
        u=Trie[u].son[v];
    }
    return ans;
}
int main () {
    cnt=1;
    cin>>n;
    for (int i=1;i<=n;i++) scanf("%s",s),insert(s);
    getFail();
    scanf("%s",s);
    printf("%d\n",query(s));
    return 0;
}