定义
一个串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作为末尾添加这个状态。
然后就完成了。