一些定理和定义就不说了史上最通俗的后缀自动机详解 - KesdiaelKen 的博客 - 洛谷博客这里都有。
我们具体说一下后缀自动机初学者难理解的地方。
什么是后缀自动机:
本质就是一个 $\tt Trie$ 树和 $\tt parent$ 树重叠的东西。
三种分类讨论怎么理解:
首先我们清楚我们加入的是一个点(字符 c
)而不是一个串,我们加入了一个点,我们通过 $\tt Trie$ 树的转移不妨设其为 $\tt trans[u][c]$ 表示在 $u$ 之后加入一个字符 c
得到的位置。
显然从空节点开始走到达某个节点 $u$ 构成一个字符串,同时也是原串的子串。
为了方便说明我们不妨设没有加入字符 c
的串是原串,加入之后为当前串。
- 第一种情况
我们通过跳后缀链接,尝试找到是否存在原串后缀加上节点 c
得到的节点。如果不存在那么我们新加入的 c
显然是不存在的,我们直接链接根节点即可。
为什么说是不存在的,考虑直接从根节点都走一步都走不到
c
。
- 第二种情况
我们幸运地找到了当前串的后缀,不妨设节点为 $u$,而且更加幸运的是 $u$ 表示集合与 $\tt trans[u][c]$ 表示集合是连续的。也就是加入当前节点之后所表示的 $\tt endpos$ 集合都增加了 $n$ 所以我们可以直接加入。
不用更新之前节点的原因是,加入之前已经存在了 $c$ 不需要再次更新了。
我们要清楚首先 $u$ 肯定是原串的后缀,然后因为通过了 $\tt Trie$ 和最长长度的双重限制,显然 $\tt trans[u][c]$ 中只有一种长度的串,而且全部都是 $u$ 所构成的串加入了一个 $c$ 所以才可以直接加入。
- 第三种情况
我们虽然找到了后缀,但是因为集合是不连续的,也就是不能确定 $\tt trans[u][c]$ 一定是当前串的后缀,但是其肯定存在一部分是当前串的后缀,我们考虑将其拆点,拆成两部分,一部分是满足条件 $2$ 的限制,另一部分就是剩下的了。
别忘记了我们需要更新之前后缀链接连接到更改位置的节点,那么我们需要连接到哪里呢?很简单,因为原来的点,也就是当前两个集合的并集,那么我们只要连接到深度浅的集合即可。
【模板】后缀自动机 (SAM) - 洛谷 的部分代码:
1 | void add(int c) { |