对于一个算法或者数据结构的学习, 我们首先要弄清它的概念, 然后理解它的构建, 进而是实现和复杂度分析, 最后考虑如何应用它.
现在学习的是 SAM, Suffix Automaton, 后缀自动机.
推荐陈立杰的冬令营讲稿. https://wenku.baidu.com/view/90f22eec551810a6f4248606.html
什么是自动机?
有限状态自动机的功能是识别字符串. 令一个自动机 $A$ , 若 $A$ 能识别字符串 $S$ , 则 $A(S) = True$ ; 若不能识别字符串 $S$ , 则 $A(S) = False$ . 特别地, 后缀自动机能识别一个字符串的所有后缀. 例如, 令 $S = ababb$ , 对 $S$ 构建后缀自动机 $A$ , 则 $A(bb) = True$, $A(abb) = True$, $A(baa) = False$.
如何用一些量刻画一个自动机? 我们需要用一个五元组: 字符集, 状态集, 初始状态, 结束状态, 转移函数.
自动机 $A$ 能识别的字符串 $S$ , 要满足 $trans(A, S)\in 结束状态$ , 记作 $Reg(A)$ . 从自动机 $A$ 的某个状态 $s$ 作为起始节点, 能识别的字符串 $S$ , 要满足 $trans(s, S)\in 结束状态$ , 记作 $Reg(s)$ .
后缀自动机的定义
对于字符串 $S$ , 对应后缀自动机 SAM .
SAM 是能识别字符串 $S$ 的所有后缀的自动机. 即 $SAM(s) = True$, 当且仅当 $s$ 是 $S$ 的后缀.
同时在后面我们能看出, SAM 能识别字符串 $S$ 的所有子串.
最简单的实现
将每个后缀插入 Trie树 中. 状态集为 Trie树 的 $V$ , 起始状态为 Trie树 的 Root, 结束状态为 Trie树 的叶子节点, 转移函数为 Trie树 的边.
时间复杂度和空间复杂度均为 $O(n^2)$ . 这可能很不能接受.
考虑进行优化, 减少 SAM 的状态数.
最简状态后缀自动机
顾名思义, 就是状态数最少的 SAM, 后面我们可以证明状态数为 $O(n)$ .
我们需要先研究它的性质, 对它充分理解并应用, 基于此才能构建出 最简状态SAM .
为了研究的方便, 我们设定记号 $ST(str) = trans(init, str)$ , 即从开始状态开始读入字符串 str 之后, 能到达的状态.
令母串为 $S$ , 它的后缀集合为 $Suf$ , 连续子串集合为 $Fac$ .
从位置 $a$ 开始的后缀为 $Suffix(a)$.
$S[l,r)$ 表示 $S[l : r-1]$, 下标从 $0$ 开始.
对于一个字符串 $s$, 若 $s\in Fac$ , 则 $ST(s)\ne null$ . 因为不能放过可能识别到后缀的可能性.
对于一个字符串 $s$, 若 $s\not\in Fac$ , 则 $ST(s) = null$ . 因为既然不是子串, 那么也不可能识别到后缀, 我们要最简状态数.
对于 $ST(s)$ , 假设 $x\in Reg(ST(s))$ , 则 $sx\in Reg(A)$ , 所以 $sx$ 为 $S$ 的后缀, 所以 $x$ 为 $S$ 的后缀.
所以对于一个状态 $a$ , $Reg(a)$ 为 $S$ 的某些后缀的集合.
更具体地, 如果 $s$ 在 $[l,r)$ 出现过, 那么 $Suffix(r)\in Reg(ST(s))$ .
记 $Right(s) = \left\{ r_1,r_2,...,r_n \right\}$ , 则 $Reg(ST(s))$ 由 $Right(s)$ 决定.