广义后缀自动机是后缀自动机(SAM)的多串扩展。它可以识别一棵 Trie 树上从根到任意一点的路径。
广义 SAM 的每个状态同样由转移边
性质
状态数
一棵给定 Trie 树
T 的广义 SAM 的状态数为线性,即O(|T|) 。
证明:
与普通 SAM 的证法类似。容易证明两个状态的
right 集合要么是包含关系要么交集为空。因此广义 SAM 的fail 链也会构成一棵树,而每个状态parent 树上的父亲的right 集合真包含该状态的right 集合。对于有多个儿子的状态,可以将其映射到一个自己包含但儿子不包含的位置。这样原树可以转化为一棵每个点的儿子数不为1 且叶子数不超过|T| 的树,这样的树的节点数是不超过2|T|-1 的(考虑完全二叉树)。因此原命题得证。
转移数
众所周知,普通后缀自动机转移数是线性的,但广义 SAM 不具有这样的性质。
一棵给定 Trie 树
T 的广义 SAM 的转移数为O(|T|\cdot |A|) 。
证明:
已知广义 SAM 的状态数是线性的,而每个状态最多有
|A| 个转移边,因此总转移数为O(|T|\cdot |A|) 。命题得证。
为什么会和单串 SAM 不同呢?回想一下字符串
从空状态开始,在转移 DAG 上找到一棵有向生成树,树边数显然是
|s|-1 。再考虑每条非树边(p,q) ,其对应字符为c 。设从空状态出发,走树边到达状态p 的路径为u ;从q 出发,走树边到达终结状态的路径为v 。那么u+c+w 将是s 的一个后缀,且对于每条树边都不同(唯一经过了(p,q) 这条边)。将这条树边映射到这个后缀上,可得知非树边数不超过s 的不同非空后缀数,即O(|s|) 。总转移数即为树边与非树边数之和,也为O(n) 。因此原命题得证。
然而 Trie 树可能有
这棵 Trie 的形态是一棵椰子树,其中链的长度为
所以,在字符集过大时,最好不要使用广义后缀自动机。
构造
广义后缀自动机的构造方式比较多,下面介绍几种常见的构造。
离线构造
这种方法一般用于给定了 Trie 树
根据上文的性质,任何构造算法的复杂度都有一个下界为
在 Trie 树上 BFS,插入一个字符时,只需将广义 SAM 的
last 指针设为它的父亲,然后按普通后缀自动机的方式插入即可。
这样做的正确性是显然的,因为 Trie 树中之前从未出现过当前前缀。