AC 自动坤

啊不是,是 AC 自动机

引入

trie 都知道吧,不知道也没关系啊,出门左转自己去搜(说实话,这东西和 trie 有个毛关系啊),然后我们知道 trie 是用来将模式串与文本串匹配然后匹最长前缀或后缀,而不能看文本串里面有没有,而 KMP 则是用来将模式串与文本串匹配出文本串中有没有模式串(不会没关系,出门左转),但是,KMP 只能单个单个的算,效率太低,所以我们要使用一种新的东西来计算。

假如说现在有一堆模式串,和一个文本串:

模式串:
aaa
aaaabbb
abac
文本串:
aaaabbbabac

我们用 trie 搜到 aaaabbb 是 aaaabbbabac 的前缀时,由于我们要的是一堆模式串在文本串里匹配,所以此时的 trie 就需要重新跑回根然后重搜,这时间消耗太大了,于是我们思考,是否可以通过一个更快捷的办法,跑到另一个可以匹配的位置,或者说是一个可匹配的位置的离终点最近的位置呢,由于我们不可能随机去找,所以必然得与已经搜了的有点关联,所以我们可以取其它串与之的最长前缀,这样就可以快速枚举与这个串有前缀的所有模式串,并保证是可以搜到可以匹配的位置的节点(哇好难理解啊,不过学过 KMP 的就用 KMP 来理解吧),那么我们如何维护最长前缀呢,首先我们可以定义一下 fail 为最长前缀。

哦对了,我们需要以 trie 为基础,然后实现 fail 等操作

考虑递推,而且由于 fail 不仅仅是往自己祖先找,所以得用广搜,根节点的 fail 值肯定指向根,或者说没有,然后找到所有可能的出边,设当前点为 u,下一个要处理的点为 v,那么如果 v 这条边存在,那么我肯定要处理,所以加入队列,并且我肯定存在 fail,那么我的 fail 将会是我父亲的 fail 的下一个与我的花色相同的(如果存在父亲的话,如果没有那就是 0),不过我怎么保证我的父亲的 fail 一定存在我的花色的边呢?那么这个问题就转换为当前要处理的点不存在这条边的情况,那不存在这条边,也就没有 fail,可是根据上面的如果这个的父亲没有这条出边,就需要继续往 fail 找,时间复杂度又巨大了(啊不是,就是正常来说,我们需要继续找父亲的 fail,所以我这里记忆化掉了,直接指向),所以我们考虑记忆化搜索,直接将这个出边指向父亲的 fail 的这条出边(同理前提是有父亲),那么这段代码如下(好好理解,这是终点,没写错,就是终点!):

bool Record(int &x, int c, int fa) {
  return ((~x) ? (q[++r] = x, fail[x]) : (x)) = fa ? nxt[fail[fa]][c] : 0;
}

x 为这条出边的指向,c 为指向的花色,u 为指向的源头也就是 x 的父亲

那么这段代码是说,如果这条出边存在,那么处理 x 并更新 fail,并将 fail 指向父亲的 fail 的同花色的边,如果这条出边不存在,那么将这条出边指向父亲的 fail 的同花色的边。

那么其他代码也非常容易,学过 trie 的都能理解(bushi,直接上代码吧:

struct Tree {
  int nxt[MaxN][27], fail[MaxN], q[MaxN], h[MaxN], cnt[MaxN], l, r, tot;

  Tree() {
    fill(nxt[0], nxt[MaxN], -1), fill(fail, fail + MaxN, 0), fill(cnt, cnt + MaxN, 0), tot = 0;
  }

  void insert(string s, int id) {
    int p = 0;
    for (int i = 0; i < s.size(); i++) {
      int c = s[i] - 'a';
      (~nxt[p][c]) || (nxt[p][c] = ++tot);
      p = nxt[p][c];
    }
    h[id] = p, cnt[p]++;
  }

  bool Record(int &x, int c, int fa) {
    return ((~x) ? (q[++r] = x, fail[x]) : (x)) = fa ? nxt[fail[fa]][c] : 0;
  }

  void get_fail() {
    r = 0, l = r + 1;
    for (int i = 0; i < 26; i++) {
      (~nxt[0][i]) ? (Record(nxt[0][i], i, 0)) : (nxt[0][i] = 0);
    }
    while (l <= r) {
      int u = q[l++];
      for (int i = 0; i < 26; i++) {
        Record(nxt[u][i], i, u);
      }
    }
  }

  void query(string x) {
    long long res[MaxN] = {0}, p = 0;
    for (int i = 0; i < x.size(); i++) {
      int c = x[i] - 'a';
      p = nxt[p][c], res[p]++;
    }
    for (int i = r; i >= 1; i--) {
      res[fail[q[i]]] += res[q[i]];
    }
    for (int i = 1; i <= n; i++) {
      cout << res[h[i]] << '\n';
    }
  }
} st;

啊,多么奇怪高级而又丑陋的代码啊!

posted @ 2024-06-29 23:19  yabnto  阅读(9)  评论(0编辑  收藏  举报