AC 自动机 [学习笔记]

独自空忆成欢 提交于 2020-03-10 19:34:13

突然想起来还有这么一个东西我没有写过学习笔记的

AC 自动机的三个板子

首先你需要会字典树,最好会 kmp ,不会也无所谓。

AC 自动机我个人觉得和 \(kmp\) 没啥联系,只和字典树有关系2333,因为 AC 自动机是基于字典树上的bfs建造出来的。

如果给你几个串,你 \(kmp\) 的复杂度显然不可以接受,所以就需要我们的 AC 自动机了。

我个人习惯从 \(cnt = 1\) 开始,即字典树的 \(root\)\(1\),以及 \(ch_{u,i}\) 来表示字典树的节点,我更倾向于把字典树的一个节点当做是字符串,因为从根节点顺次下来确实是一个字符串。

首先你要明白 \(fail_i\)\(i\) 的关系是 \(fail_i\)\(i\) 的子串,以及,当你构造完 AC 自动机之后,\(ch_{u,i}\) 不一定是原字典树的样子,就是说会相对于字典树的结构变化,比如说跨越层,但是并不难理解,其实就是为了 \(fail\) 的转移。

如果实际需要,比如说 这一题,你就可以先复制原有节点,然后建 AC 自动机。

\(fail_i -> i\) 连边会成为一棵树,而树内查询之类的可以用树状数组等数据结构维护,其中有一条重要性质:\(i\) 的子树内所包含的节点,都是包含 \(i\) 这个子串的(我上面说过我喜欢把节点作为字符串了

总感觉不是很好讲,那我来画图吧。 举例子是最好的办法。 为了方便画图,我给出两个字符串 "ab" "bab"

图1: 建成一颗字典树

图2:建一个 AC 自动机

如何构建呢?我把构建好的放出来。在这个图里 \(i\) 指向 \(fail_i\)
举个栗子,我们先找到 "bab" 对应的节点 \(i\) ,然后你发现 \(fail_i\) 指向的节点所对应的字符串是 "ab",以及找到其他的 \(fail_i\)\(i\) 的关系都是这样。
\(fail_i\) 所对应的字符串是 \(i\) 所对应字符串的子串并且是 \(i\) 的后缀。

如果我是 "abca" "babd" 呢,原有的 AC 自动机不用做任何变化,接着画就好了。

诶,然后你发现你的 "abc" 没办法找到一个 \(fail_i\),没关系,那就连到根节点,然后你会发现你的 "babd"也找不到一个相同后缀的子串,没关系,那也丢给根节点。

然后画成这个 b 样子了

然后你发现你的 "abca" 只能找到一个短短的相同后缀 "a",那就连上,所以 AC 自动机就建好了。

我们尝试画一个 AC 自动机的 \(fail\) 树。(其实就是把 \(fail_i\)\(i\) 连起来嘛…)

我们就很自然的发现了 AC 自动机 \(fail\) 树的特点了(虽然我前面提到过)

代码

  std ::queue<int> q;
  for (int i = 0; i < 26; i++)
    if (ch[1][i])
      fail[ch[1][i]] = 1, q.push(ch[1][i]);
    else
      ch[1][i] = 1;
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    for (int i = 0; i < 26; i++)
      if (ch[u][i])
        fail[ch[u][i]] = ch[fail[u]][i], q.push(ch[u][i]);
      else
        ch[u][i] = ch[fail[u]][i];
  }
  for (int i = 2; i <= cnt; i++) g[fail[i]].pb(i);

然后我们来看例题 P3808 【模板】AC自动机(简单版)

给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过。

我们直接对 \(n\) 个模式串建 AC 自动机,然后在上面跑就好了,一直跳 \(fail\),查询以当前结尾的串有多少个子串的 \(end\) 节点是这个,用 \(ed_i\) 表示这个出现次数,不能反复计算,所以计算过的就弄成 \(-1\)
也不会忽略上面的贡献,因为如果 \(ed_i == -1\),那么 \(fail_i,fail_{fail_i}\) 也一定是 \(-1\)

int find(string s) {
    int ans = 0 , p = 0 ;
    for(char c : s) {
        int x = c - 'a' ;
        p = ac[p].son[x] ;
        for(int j = p ; j && ac[j].end != -1 ; j = ac[j].fail) 
            ans += ac[j].end , ac[j].end = -1 ;
    }
    return ans ;
}

咕咕咕

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!