参考了一些博客,如有侵权,请告知。
内部资料,包不外传。
定义
后缀自动机(SAM)的结构包含两部分,有向无环单词图(DAWG)和 parent 树。SAM 中的每个节点都同时存在于这两个结构中。
以下假设我们是关于字符串 \(s\) 的 SAM。
DAWG
DAWG 是一个 DAG。
我们令起始结点为 \(st\),\(st\) 在 DAWG 中表示一个空串,而其他所有结点均 \(s\) 的一个或多个子串。
对于 DAWG 中的每条有向边都有一个字符 \(c\),一个结点 \(x\) 所包含的子串为从 \(st\) 走到 \(x\) 路径上的字符依次拼接得到的结果,因为有多条路径,所以结点 \(x\) 可能包含很多子串。
为了描述方便,如果结点 \(x\) 包含 \(s\) 的一个后缀,那么我们称这个点为关键点。
结点
- 每一个结点代表的所有子串,一定是 \(s\) 某个前缀中若干长度只相差 \(1\) 的后缀。
- 结点 \(x\) 代表的所有子串中,最长子串长度记为 \(\max ( x )\),最短子串长度记为 \(\min ( x )\)。
- 我们记 endpos 为一个子串在 \(s\) 中所有出现过的结尾位置集合,则一个结点所有子串的 endpos 相同。
- 任意两个结点的 endpos 不同,否则即可将其合并为一个结点。
有向边
\(u \to v\) 有连边则表示 \(u\) 的所有子串加上这条边所代表的字符 \(c\) 后能够变成的子串被 \(v\) 包含,但不一定其是 \(v\) 的所有子串(\(v\) 可能由多个点连边转移过来)。
后缀链接与 parent tree
注意:后缀链接并没有和 DAWG 中的有向边有直接联系。
- 记 \(u\) 的后缀链接所指的结点为 \(nxt_u\),如果 \(nxt_u = v\),当且仅当 \(\min(u) = \max (v) + 1\),且 \(nxt_u\) 的子串是 \(u\) 的子串的后缀。
- 对于 \(u\) 和 \(nxt_u\) 来说,\(nxt_u\) 的 endpos 集合包含 \(u\) 的 endpos 集合。
- 后缀链接本质构成了一棵以 \(st\) 为根的内向树,我们称这棵树为 parent tree。
构造
大家可以通过构造感受 SAM 的有向边和后缀链接所表示的深层含义。
正常情况下,构造 SAM 的复杂度是 \(O(nV)\) 的,\(V\) 为字符集大小。
SAM 的构造是一个在线的增量算法,也就是说我们通过 \(s\) 的 SAM 得出 \(s + c\) 的 SAM。一般情况下我们选择直接记代码,当然你也可以通过下述例子感受 SAM 的构造过程与严谨逻辑。
首先,当 \(s\) 为 acbabc
时,我们的后缀自动机应该长这样(比较复杂,其实是为了让读者更清楚的理解 SAM 的结构,注意看清楚箭头方向):
解释一下其中的东西:
- 虚线表示有向边,实边表示后缀链接,\(u \to nxt_u\) 有一条有向的实边(后缀链接)。
- \(v_1\) 为 \(st\) 结点,也就是起始结点,其中 \(v_2, v_3, v_4, v_5, v_6, v_7\) 代表子串之一是 \(s\) 的一个后缀,其中 \(v_7\) 为 \(s\) 串,然后从 \(v_6\) 到 \(v_2\) 长度递减,这些点也就是我们上面说过的关键点。
- 为了方便,我们将把 \(v_2, v_3, ..., v_9\) 这些结点所代表的字符串表示出来,如果你不记得表示哪些字符串了,可以看一眼。
-
- \(v_2\):