昨天看了一下午后缀自动机,终于有了一点心得,特地来做一下笔记。
Definitions
首先不加证明地给出几个定义和引理:
DFA(有限状态自动机)
有限状态自动机的功能是识别字符串,令一个自动机A,若它能识别字符串S,就记为A(S)=True,否则A(S)=False。
自动机由五个部分组成,alpha:字符集,state:状态集合,init:初始状态,end:结束状态集合,trans:状态转移函数。
不妨令trans(s,ch)表示当前状态是s,在读入字符ch之后,所到达的状态。
如果trans(s,ch)这个转移不存在,为了方便,不妨设其为null,同时null只能转移到null。
null表示不存在的状态。
同时令trans(s,str)表示当前状态是s,在读入字符串str之后,所到达的状态。
后缀自动机
就是能够识别一个串的所有后缀的自动机。后面我们可以知道,他也可以识别子串。
right集合
对于一个状态,我们认为这个状态代表了一个前缀长度为((len_{fa}, len_s])的所有后缀,他们的right集合都是相同的。
对于right集合,我们有两个状态的right集合要么不相交,要么一个是另一个的子集。所以我们结合这种关系,可以构造出一棵parent树,其中fa是right阶最小的包含孩子的状态。可以发现,考虑到每个状态到start的路径是连续的,所以这个parent树就是反串的后缀树。
后缀自动机的目标就是去维护所有状态的上述性质,并且考虑到状态是连续的子串,我们还需要去维护他们之间的转移。
How to construct
我们采用增量法来构造后缀自动机。
假设现在的状态为p,新的状态为np,从p的所有子串映射到np的所有子串要添加的字符为c,我们显然首先要添加一条从p到np标号为c的边,然后,因为每个状态到根的路径都代表了上一个字符的后缀,所以,这条路径上的所有节点增加一个字符之后,都可以成为新串的后缀,都有可能带来right集合的改变,既然我们要维护right集合,我们就要去查看这些状态。
考察这条路径上的节点(p_i),假设(p_i)没有一条标号为c的边,那很好,我们直接连边(p_i)到q即可。
对于节点(p_i)已经有了一条标号为c的边,我们设原来可以转移到状态q,那么我们新增的字符c就一定会带来q的改变。
有两种情况:
- 如果(len_q = len_{p_i}+1),那么q的所有子串都是由p转移过来的,那么我们直接在right集合中新增一个最后的节点就好了。
- 如果(len_q > len_{p_i} + 1),在这种情况下,q的子串不一定从p转移过来,我们把这些子串拆成两个集合,第一个集合为从p转移过来的,记为nq,即(len_{nq} = len_{p_i}+1),考虑到p的所有fa都是p的后缀,所以我们把所有的fa全部把连向q的边改为连向nq,根据之前的推理,q仍然有边连向。nq的除了right的所有属性与q一致。
考虑这样做之后的par树关系,不难发现,我们应该设(fa_q = fa_{np} = q)。
Application
下面不加证明地给出几个SAM的性质。
- 每个状态s代表的串的长度是区间((len_{fa_s}, len_s])。
- 每个状态s代表的所有串在原串中的出现次数及right集合相同。
right集合可以使用平衡树启发式合并来求。 - 在parent树中,每个状态的right集合是他的父状态right集合的子集。
- SAM的parent树是原串的反向前缀树。
反向前缀树的定义是:把每一个前缀的反串插入到一个trie中,并且把没有分支的链合并。
这个性质是容易发现的。考虑一个前缀在后缀自动机上的状态,我们一直沿fa指针走,每次都会变成当前串的一个后缀,直到空串,反过来看这个过程,就是在沿着反向前缀树从上往下走。
那么,如果我们求出反串的SAM,那么他的parent树就是原串的后缀树。
Materials
下面列出了我在学习SAM的时候使用的一些学习资料。
[1]陈立杰冬令营营员交流
[2]fanhq的博客
[3]后缀自动机及其应用——张天扬,2015国家队论文