学习笔记:AC 自动机

AC 自动机

引入

AC 自动机,顾名思义就是(能够帮你自动切题的机器)一种可以很方便地进行多模式串匹配的东西。

前置知识

Trie 树

KMP

实现

AC 自动机是以Trie 树的结构为基础,结合KMP的思想建立的。

简单来说,建立 AC 自动机主要有两个步骤:

  1. Trie 树的结构基础:将所有待匹配的模式串构造成一棵 Trie 树。
  2. KMP 的思想:对 Trie 树上的所有节点构造失配指针。

字典树构建

AC 自动机在初始时会将若干个模式串丢到一个 Trie 里,然后在 Trie 上建立 AC 自动机。这个 Trie 就是普通的 Trie,该怎么建怎么建。

这里需要仔细解释一下 Trie 的结点的含义,尽管这很小儿科,但在之后的理解中极其重要。Trie 中的结点表示的是某个模式串的前缀。我们在后文也将其称作状态。一个结点表示一个状态,Trie 的边就是状态的转移。

形式化地说,对于若干个模式串 $s_1,s_2\dots s_n$,将它们构建一棵字典树后的所有状态的集合记作 $Q$。

失配指针

AC 自动机利用一个 fail 指针来辅助多模式串的匹配。

状态 $u$ 的 fail 指针指向另一个状态 $v$,其中 $v\in Q$,且 $v$ 是 $u$ 的最长后缀(即在若干个后缀状态中取最长的一个作为 fail 指针)。对于学过 KMP 的朋友,我在这里简单对比一下这里的 fail 指针与 KMP 中的 next 指针:

  1. 共同点:两者同样是在失配的时候用于跳转的指针。
  2. 不同点:next 指针求的是最长 Border(即最长的相同前后缀),而 fail 指针指向所有模式串的前缀中匹配当前状态的最长后缀。

因为 KMP 只对一个模式串做匹配,而 AC 自动机要对多个模式串做匹配。有可能 fail 指针指向的结点对应着另一个模式串,两者前缀不同。

没看懂上面的对比不要急,你只需要知道,AC 自动机的失配指针指向当前状态的最长后缀状态即可。

AC 自动机在做匹配时,同一位上可匹配多个模式串。

构建指针

下面介绍构建 fail 指针的 基础思想

构建 fail 指针,可以参考 KMP 中构造 Next 指针的思想。

考虑字典树中当前的结点 $u$,$u$ 的父结点是 $p$,$p$ 通过字符 c 的边指向 $u$,即 $trie[p,\mathtt{c}]=u$。假设深度小于 $u$ 的所有结点的 fail 指针都已求得。

  1. 如果 $\text{trie}[\text{fail}[p],\mathtt{c}]$ 存在:则让 u 的 fail 指针指向 $\text{trie}[\text{fail}[p],\mathtt{c}]$。相当于在 $p$ 和 $\text{fail}[p]$ 后面加一个字符 c,分别对应 $u$ 和 $fail[u]$。
  2. 如果 $\text{trie}[\text{fail}[p],\mathtt{c}]$ 不存在:那么我们继续找到 $\text{trie}[\text{fail}[\text{fail}[p]],\mathtt{c}]$。重复 1 的判断过程,一直跳 fail 指针直到根结点。
  3. 如果真的没有,就让 fail 指针指向根结点。

如此即完成了 $\text{fail}[u]$ 的构建。

一些例题

P3808 【模板】AC 自动机(简单版)

#include <iostream>
#include <queue>
#define MAXN 1000005
using namespace std;
int n;
char s[MAXN];
int trie[MAXN][26];
int tag[MAXN], fail[MAXN];
int tot = 1, root, ch;
int ans;
queue <int> q;
int main(){
    ios::sync_with_stdio(false);
    cin >> n;
    for(int i = 1 ; i <= n ; i ++){
        cin >> s + 1;root = 0;
        for(int j = 1 ; s[j] != 0 ; j ++){
            ch = s[j] - 'a';
            if(trie[root][ch] == 0)
                trie[root][ch] = ++tot;
            root = trie[root][ch];
        }
        tag[root]++;
    }
    for(int i = 0 ; i < 26 ; i ++)
        if(trie[0][i] != 0)
            q.push(trie[0][i]);
    while(!q.empty()){
        root = q.front();q.pop();
        for(int i = 0 ; i < 26 ; i ++){
            if(trie[root][i] != 0){
                fail[trie[root][i]] = trie[fail[root]][i];
                q.push(trie[root][i]);
            }else trie[root][i] = trie[fail[root]][i];
        }
    }
    cin >> s + 1;root = 0;
    for(int i = 1 ; s[i] != 0 ; i ++){
        ch = s[i] - 'a';
        root = trie[root][ch];
        for(int j = root ; j != 0 && tag[j] != -1 ; j = fail[j])
            ans += tag[j],tag[j] = -1;
    }
    cout << ans << endl;return 0;
}

P3796 【模板】AC 自动机(加强版)

#include <iostream>
#include <cstring>
#include <queue>
#define MAXN 155
#define MAXL 1000005
using namespace std;
int n, tot, root, ch, ans;
char s[MAXL][MAXN], t[MAXL];
int trie[MAXL][26], fail[MAXL];
int idx[MAXL], cnt[MAXL], val[MAXL];
queue <int> q;
int main(){
    ios::sync_with_stdio(false);
    while(true){
        cin >> n;
        if(n == 0)break;
        memset(trie, 0, sizeof(trie));
        memset(fail, 0, sizeof(fail));
        memset(idx, 0, sizeof(idx));
        memset(cnt, 0, sizeof(cnt));
        memset(val, 0, sizeof(val));
        tot = 0;ans = 0;
        while(!q.empty())q.pop();
        for(int i = 1 ; i <= n ; i ++){
            cin >> s[i] + 1;root = 0;
            for(int j = 1 ; s[i][j] != 0 ; j ++){
                ch = s[i][j] - 'a';
                if(trie[root][ch] == 0)
                    trie[root][ch] = ++tot;
                root = trie[root][ch];
            }
            idx[root] = i;
        }
        for(int i = 0 ; i < 26 ; i ++)
            if(trie[0][i] != 0)
                q.push(trie[0][i]);
        while(!q.empty()){
            root = q.front();q.pop();
            for(int i = 0 ; i < 26 ; i ++){
                if(trie[root][i] != 0){
                    fail[trie[root][i]] = trie[fail[root]][i];
                    q.push(trie[root][i]);
                }
                else trie[root][i] = trie[fail[root]][i];
            }
        }
        cin >> t + 1;root = 0;
        for(int i = 1 ; t[i] != 0 ; i ++){
            ch = t[i] - 'a';root = trie[root][ch];
            for(int j = root ; j != 0; j = fail[j])val[j]++;
        }
        for(int i = 0 ; i <= tot ; i ++)
            if(idx[i] != 0){
                ans = max(ans, val[i]);
                cnt[idx[i]] = val[i];
            }
        cout << ans << endl;
        for(int i = 1 ; i <= n ; i ++)
            if(cnt[i] == ans)
                cout << s[i] + 1 << endl;
    }
    return 0;
}

P5357 【模板】AC 自动机(二次加强版)

#include <iostream>
#include <queue>
#include <cstring>
#define MAXN 2000005
using namespace std;
int n, tot = 1, root, ch, len, tmp;
char s[MAXN], t[MAXN];
int trie[MAXN][26], fail[MAXN], flag[MAXN], ans[MAXN];
int vis[MAXN + 50], rd[MAXN], cnt[MAXN];
queue <int> q;
int main(){
    ios::sync_with_stdio(false);
    cin >> n;
    for(int i = 1 ; i <= n ; i++){
        cin >> s + 1;root = 1;
        len = strlen(s + 1);
        for(int i = 1 ; i <= len ; i ++){
            ch = s[i] - 'a';
            if(trie[root][ch] == 0)
                trie[root][ch] = ++tot;
            root = trie[root][ch];
        }
        if(flag[root] == 0)
            flag[root] = i;
        cnt[i] = flag[root];
    }
    for(int i = 0 ; i < 26 ; i ++)
        trie[0][i] = 1;
    q.push(1);
    while(!q.empty()){
        root = q.front();q.pop();
        tmp = fail[root];
        for(int i = 0 ; i < 26 ; i ++){
            ch = trie[root][i];
            if(ch == 0){
                trie[root][i] = trie[tmp][i];
                continue;
            }
            fail[ch] = trie[tmp][i];
            rd[fail[ch]]++;
            q.push(ch);
        }
    }
    cin >> t + 1;root = 1;
    len = strlen(t + 1);
    for(int i = 1 ; i <= len ; i ++){
        ch = t[i] - 'a';
        root = trie[root][ch];
        ans[root]++;
    }

    for(int i = 1 ; i <= tot ; i ++)
        if(rd[i] == 0)
            q.push(i);
    while(!q.empty()){
        root = q.front();q.pop();
        vis[flag[root]] = ans[root];
        ch = fail[root];
        rd[ch]--;
        ans[ch] += ans[root];
        if(rd[ch] == 0)
            q.push(ch);
    }
    for(int i = 1 ; i <= n ; i ++)
        cout << vis[cnt[i]] << endl;
    return 0;
}
posted @ 2023-10-04 08:26  tsqtsqtsq  阅读(19)  评论(0)    收藏  举报  来源