AC自动机学习笔记

前置知识

Trie树(字典树),KMP(思想)

AC自动机全名(这个不重要),是用于字符串匹配的玩意。

我们知道,在一个文本串里匹配模式串可以用KMP,但如果是匹配多个文本串呢?

假设有\(2\times 10^5\)个模式串,又该如何应对呢?这时就要用到AC自动机了。

先看一个
文本串:
abcabbaab
模式串:
abba
ab
abc
bb
bc

很容易想到的做法是5次KMP,然后输出答案。但是数据大点你就直接炸了。

首先建一下Trie树

这里红色结点表示这个点为某一个串的末尾。

然后该怎么做?

先遍历,abc,成功匹配到一个模式串。接下来干啥?难道直接返回根节点重新匹配吗?这样根跑\(n\)次KMP没什么区别。

所以我们重头戏,失配指针\(fail\) TA来了。

我们看,abc,bc共有 bc这一部分。所以我能不能直接省去返回根节点,直接从3号节点跳到8号节点呢?

可以。但是得分情况。

如果他们只是有一部分重,比如acbac这样就不行。因为你不能保证跳完之后ac是否一定有一个b作为开头。

那是不是只要有开头重合...肯定不对。这很显然。

必须前一个串的后缀与后一个串的前缀重合,那我就能直接从后缀末尾跳到前缀的末尾。

那这个玩意怎么求?

\(fail\)指针一定比跳之前深度浅。

这很显然。

假设一个字符串长度为\(n\)

简单的证明:

后缀位置末尾:第\(n\)

前缀长度\(\le n\)

前缀末尾\(\le\)\(n\)

最好的情况就是前缀长度=后缀长度=\(n\)那这俩串一定重合,所以不成立。


通过这个结论,我们可以推出第一层\(fail\)指针一定指向\(root\)(根节点)

我们还可以想到用bfs搜索。因为\(fail\)指针根深度会变得越来越浅,所以我们用bfs求到该层上一层已经全部求完了。

那我们是不是可以用KMP思想通过父节点的\(fail\)求出子结点的\(fail\)呢?

当然可以啦。

因为父亲节点的\(fail\)代表以父亲为末尾的后缀已经有一个前缀与他匹配。


接下来举个例子(只是思想,并非真正操作)

树和串根本不是一个东西啊喂(#`O′)

假设已经匹配的是这两个
X不是字符,只是表示上下不一样

XXXabcd
abcdeXX

那么abcd为公共的前后缀。

那么从前一个串的d可以跳到后一个串的d

现在我的第一个串有个新来的儿子e,变成这样
XXXabcde
abcdeXX
(这里少一个)其实无所谓,反正要那部分重合的就行。

我们发现第二个串已经匹配的前缀正好也有个儿子e,那我肯定能调过来。

我第一个串又来了个儿子'f'变成

XXXabcdef
abcdeXX

先跳父亲e\(fail\),来到第二个串
我们再看第二个串,咦?没有f?

那我只能再跳第二个串e\(fail\),以此往复

那求出来\(fail\)又有啥用呢?

我们先求出来(想自己找的可以回去上面找一下\(fail指针试试\)

有点抽象是吧,那我简化一下。

现再当求了一个

字符串符合题意时,那TA的\(fail\)一定符合题意\(后缀嘛。是原来的一部分\)。然后继续匹配就能快乐的完成了。

posted @ 2026-02-28 10:29  NumLuck  阅读(3)  评论(0)    收藏  举报