定义
一个串
S
的后缀自动机是一个有限状态自动机,它可以且仅可以识别
后缀自动机的构造
一些记号
- 母串
s
,标号为
[0,|s|) -
s[l,r]
表示母串的第
l
到第
r 个字符构成的子串 - 从第
i
个位置开始的后缀记为
sufi - 到第
i
个位置为止的前缀记为
prefi - statestr 表示从初始状态读入 str 这个字符串到达的状态
- rightstr 表示字符串 str 在 s 中每一次出现的右端点集合
rightstate 表示状态 state 代表的所有串出现的右端点集合- parent 树为 right 集合相互包含关系所构成的一棵树
- lenstate 表示状态 state 所代表的最长的子串
性质
- 不同状态的
right
集合要么不相交,要么是包含关系
证明:设 r0∈rightA∩rightB,states[l0,r0]=A,states[l1,r0]=B
不失一般性地,令 l0<l1 ,于是 s[l1,r0] 是 s[l0,r0] 的一个后缀,那么 A 状态能到达的状态,B 集合必定能到达,所以 rightA⊂rightB 。 - 每个状态
state
代表的所有不同的串在原串中出现的次数,恰好就是
rightstate
的大小。
证明:由于状态之所以可以代表多个串是因为它们的 right 集合相同。一个串在母串中可以由长度 len 和它出现的右端点 r 确定。这里对于一个确定的串str ,它的长度已经确定了。于是每一个右端点确定了一个它在母串的位置,于是这个串 str 在母串中出现的次数恰好就是 rightstr ,于是每个 state 代表的子串它在母串中出现的次数恰好就是 rightstate - 设
statestr=A
,那么
A
在
parent 树上的父亲 faA 代表的所有串必定是 str 的后缀。
证明:由于 stateA⊂statefaA ,利用类似于第一条性质的证明思路,容易得到。 - 状态 state 所代表的所有串的长度是区间 (lenfastate,lenstate]
- 一个状态 state 存在一个 char 的转移,那么 fastate 也存在一个 char 的转移
- 任意一个
A
的后继状态
B ,必定满足 lenB≥lenA+1
证明:设 A 所代表的最长的子串为str ,那么存在转移 statestr+char=B ,于是 B 可以代表str+char 这个子串。于是 B 状态所代表的所有子串的最小长度至少为|str|+1 即 lenA+1
后缀自动机的构造算法
首先空串的后缀自动机t是一个始状态
start
,
lenstart
显然为
0
。
假设我们已经得到了串
statestr+ch
显然不在
str
的后缀自动机中,新建一个状态
statestr+ch=end
,
lenend
显然为
|str|+1
令
statestr=last
,那么
end
必定是
last
的一个后继状态,同理必定是
falast
的一个后继状态。
那么对于本来就不存在这个转移的节点我们就直接加入这个转移就可以了,对于存在这个转移的状态,我们需要在它的
right
集合中加入
|str|+1
假如
last
的所有祖先都不存在
char
这个转移,那么我们需要
- 将 end 的父亲设为始状态 start
- 将 lenend 设为 1
否则我们可以找到第一个存在这个转移的节点
记
anc
沿
char
转移的状态为
fail
。
那么
lenanc
和
lenfail
可能有两种关系
-
lenanc+1=lenfail
,那么需要
- 将 fail 设为 end 在 parent 树上的父亲
- 在 fail 的 right 集合中加入 |str|+1
-
lenanc+1<lenfail
,显然对于长度属于
(l+1,lenfail]
部分的串是不可以加入
|str|+1
这个元素的。因此我们需要把这个状态
fail
分裂成两部分:加入了
|str|+1
这个元素的新状态
new
和没有加入的状态。那么这里需要
- 新建一个状态 new
- 将所有关于 fail 的转移都指向 new
- 将 new 的 len 设为 lenanc+1
- 把 fail 和 end 的父亲设为 new
- 在 new 的 right 集合中加入 |str|+1
至此,新的自动机就被更新出来了。
right 集合的求法
实际上我们很多时候不需要求出
right
集合确切有哪些元素(如果真的需要就可能要做到
O(n2)
,或者用可持久化平衡树做到
O(nlogn)
),我们也许只需要知道
right
集合的大小之类的。那么由于有
parent
树这个特殊的结构,而且某个节点
v
的所有儿子节点的
- 在所有的叶子节点,也就是母串
s
的所有前缀到达的状态,加入对应前缀。也就是说
∀i∈[0,|s|),rightstateprefi+i - 对于所有的非叶子状态 v′ , rightv′=∑p∈childv′rightp
注意以上对集合的运算我用的加法其实是并。但是我们在计算同一个集合时,从来不会将同一个元素并入 2 次,所以简单地理解为加入就可以了。
而假如我们只需要求到
后缀自动机与后缀树的联系
后缀自动机的
parent
树实际上是反串的后缀树。
假如我沿后缀自动机上跑到了一个状态
state
在
parent
树上是一个叶子节点,那么这个串必定是某个前缀
prefx
我们不妨沿
state
从
parent
树上往上跳,我们发现到达的每个状态都是
prefx
的一个后缀,这有点类似于
kmp
的
fail
指针的意味(其实它的实际意义就是这样的。),和反串的后缀树相比:
- 叶子节点的确是原串的一个前缀也就是反串的一个后缀
- 任意一个节点的所有儿子节点的 LCA 就是该节点所代表的字符串
那么这显然是一棵 trie ,又因为它有最少的状态,所以我们就建出了反串的一棵后缀树。
那么具体地
- 前缀
i
的状态是反串的后缀
n−i - 链接 state 和 fastate 的边,对应原串长度为 lenstate−lenfastate
这样子就建好了。
trie上构建后缀自动机的方法
由于这里的思路还没考虑好,只把构建方式贴出来好了。
- 找出 trie 的 bfs 序
- 按照 bfs 序,用父节点的 state 作为末尾添加这个状态。
然后就完成了。