算法 - 后缀自动机 & 回文自动机
写这篇文章的用意主要是记录一下我曾经学会过 SAM。
后缀自动机
主要学习资料
- http://wyfcyx.is-programmer.com/posts/76107.html (对我的帮助最大)
- http://www.cnblogs.com/Robert-Yuan/p/5187439.html (相当详细)
- 2012年noi冬令营陈立杰讲稿.ppt (始祖)
- hiho一下 127周 和 128周。(理论结合实践)
- https://chrt.github.io/2017/04/21/suffix-automaton/ (看到这个我觉得我不用再写这篇文章了)
- https://zhuanlan.zhihu.com/p/25948077 (复杂度“证明”)
- SAM 是一种确定有限状态自动机 (DFA),它能够接受所有字符串的所有后缀。
- 重要的信息有 $tranc(x, c)$ 和 $fa(x)$,有两种理解方式:
- 自动机形态
- $tranc(x,c)$ 为状态 $x$ 接受字符 $c$ 后到达的状态
- $fa(x)$ 是 $right(x)$ 的超集中最小的所对应的状态
- 后缀树形态(逆序)
- $fa(x)$ 是 $x$ 的父节点
- $tranc(x,c)$ 是 $x$ 对应的后缀在首部增加字符 $c$ 后所形成的后缀在树中对应的结点
- 自动机形态
- 无论那种理解方式,反正可以互相转换。鉴于后缀树更为直观、简洁,那么就以这种方式来描述。
- 对于一个字符串 $s(|s|=n)$,建立它的后缀 trie,就形成了它的后缀树,但这样效率和空间都很糟糕。如果对 trie 中的单链进行压缩,那么可以证明,结点个数是 $O(n)$ 的。(证明:借用虚树复杂的证明,$n$ 个叶节点都是关键点,而关键点两两之间的 LCA 不超过 $n-1$,所以总的结点数不超过 $2n-1$ 个。)
- 接下来的关键是如何在线性时间内构造一棵压缩后的后缀树。
- 先给张图,未完待续。
一些性质
- $fa$ 指针组成的是反串的后缀树,当然是树了。$tranc$ 指针组成的是 DFA,所以是一个有向无环图(DAG),存在拓扑序。
- $tranc$ 指针使 $len$ 增加,$fa$ 指针使 $len$ 减少。
- 关键点(红色标记)自带一个 $EndPos$,然后每个结点的 $EndPos$ 集合是自己和子节点的 $EndPos$ 集合的并集,且子节点以及本身的 $EndPos$ 集合之间没有交集。
- 接受状态是 $last$ 状态到根的链上的所有状态。
- $len$ 的大小是父节点的 $len$ 加上父边压缩的字符数。
- $[len_{min},len_{max}]$ 的区间大小是由 $tranc$ 转移过来的状态的区间大小之和。
一些套路
- 如果要快速找到一个子串对应的状态,可以在后缀树上倍增。比如要找 [l, r] 的子串,那么找到 [1, r] 逆序的所在的关键点,然后往上爬 l - 1 个长度。
- $cnt$ 表示该状态的 $EndPos$ 集合的大小,可以在建完自动机后累加得到。
- 如果要进行多串匹配的话,不妨使用广义后缀自动机,往往会简单一些。
- 如果可以离线,可以用树上启发式合并得到每个点的 EndPos 集合。
- 如果要维护 EndPos 相关的信息,来一发 LCT。
- 获取拓扑序可以按照 $len$ 进行基数排序。
- 为了某个状态具体的字符串内容,可以记录 $EndPos$ 中的某一个元素。
回文自动机
相比后缀自动机,还是简单不少的。
与后缀自动机的一些不同:
- 一个结点只表示一个回文串,这个特性会带来极大的方便。
- 对于 $tranc$ 边来说,奇数和偶数都是树的形式。
- 多了一个 $num$ 表示状态对应的回文串的后缀中回文串的个数(以 $fa$ 为边的树的深度)。
- $tranc$ 边很可能比 $fa$ 边有用。
与后缀自动机的一些相同:
- $fa$ 指针使 $len$ 减少。
- $cnt$ 的含义和计算方法相同。