发现了一种 AC 自动机的科学讲法,能够更准确地描述自动机中的数学模型和代码中的对象之间的关系。

字典树模型

假设有个字符串,字符集为,这些字符串构成的字典树为。其中表示字典树的所有状态,即所有的结点,而表示字典树的转移函数,即转移边。

考虑到任意都唯一对应一个字符串,我们也认为是一个字符串集合。

AC 自动机模型

构建的 AC 自动机可以表示为

任意,与唯一对应,定义为中所有的后缀(包括)形成的字符串集合。也就是说是一个字符串集簇。不妨记这个双射为

是 AC 自动机的转移函数。对于,假设的后缀)。若存在最大的使得存在,那么(否则不定义)。

要求出,我们需要一个辅助函数。对于。这也是所谓的失配指针。有了,我们就可以观察到

引理 1:设),那么

证明:不是所有的都存在

断言 1,若存在,那么

证明:由引理 1,可以发现末尾去掉得到的集合。结合的定义,在中最大的使得存在,一定也是中最大的,那么一定有

断言 2

证明:由定义和定义。

根据上述断言可以得出 AC 自动机的代码实现:

struct AC {
  int tr[SZ][ALP], fail[SZ], tot;
  void init() { tot = -1, new_node(); }
  int new_node() { return ++tot, fail[tot] = 0, memset(tr[tot], 0, sizeof(tr[tot])), tot; }
  void insert(const char *s) {
    for(int u = 0; *s; ++s){
      int c = *s - 'a';
      if (!tr[u][c]) tr[u][c] = new_node();
      u = tr[u][c];
    }
  }
  int q[SZ], ql, qr; // q里就是BFS序
  void build() {
    ql = 1, qr = 0;
    FOR(i, 0, ALP - 1) if (tr[0][i]) q[++qr] = tr[0][i];
    while (ql <= qr) {
      int u = q[ql++];
      FOR(c, 0, ALP - 1) {
        if (tr[u][c]) fail[tr[u][c]] = tr[fail[u]][c], q[++qr] = tr[u][c];
        else tr[u][c] = tr[fail[u]][c];
      }
    }
  }
};