• AC自动机

    解释

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

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

    然后就可以利用它进行多模式匹配了。

    字典树构建

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

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

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

    失配指针

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

    状态 的 fail 指针指向另一个状态 ,其中 ,且 是 的最长后缀(即在若干个后缀状态中取最长的一个作为 fail 指针)。这里简单对比一下这里的 fail 指针与 KMP 中的 next 指针:

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

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

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

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

    构建指针

    下面介绍构建 fail 指针的 基础思想:(强调!基础思想!基础!)

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

    考虑字典树中当前的结点 , 的父结点是 , 通过字符 c 的边指向 ,即 。假设深度小于 的所有结点的 fail 指针都已求得。

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

    如此即完成了 的构建。

    例子

    下面放一张 GIF 帮助大家理解。对字符串 i he his she hers 组成的字典树构建 fail 指针:

    1. 黄色结点:当前的结点 。
    2. 绿色结点:表示已经 BFS 遍历完毕的结点,
    3. 橙色的边:fail 指针。
    4. 红色的边:当前求出的 fail 指针。
    5. 例子

    下面放一张 GIF 帮助大家理解。对字符串 i he his she hers 组成的字典树构建 fail 指针:

    1. 黄色结点:当前的结点 。
    2. 绿色结点:表示已经 BFS 遍历完毕的结点,
    3. 橙色的边:fail 指针。
    4. 红色的边:当前求出的 fail 指针。
    5.  
    6. 我们重点分析结点 6 的 fail 指针构建:

     

    找到 6 的父结点 5,。然而 10 结点没有字母 s 连出的边;继续跳到 10 的 fail 指针,。发现 0 结点有字母 s 连出的边,指向 7 结点;所以 。最后放一张建出来的图:

    字典树与字典图

    我们直接上代码吧。字典树插入的代码就不分析了(后面完整代码里有),先来看构建函数 build(),该函数的目标有两个,一个是构建 fail 指针,一个是构建自动机。参数如下:

    1. tr[u,c]:有两种理解方式。我们可以简单理解为字典树上的一条边,即 ;也可以理解为从状态(结点) 后加一个字符 c 到达的状态(结点),即一个状态转移函数 。下文中我们将用第二种理解方式继续讲解。
    2. 队列 q:用于 BFS 遍历字典树。
    3. fail[u]:结点 的 fail 指针。