一些对于SAM的理解
对于构造需要满足的要求就先省略了。
如果不考虑时空限制的话,后缀Trie是一个很方便的实现方式,但是其时空复杂度都是
- 把转移压缩(字符转移变为字串转移),这个对应后缀树。
- 压缩状态(找到等价类进行划分),这个对应后缀自动机。
现在我们考虑第二种优化方法,这时我们引入 endpos 的概念,我们用
- 对于两个子串
s 和t ,s 与t 的关系只有包含或并列两种可能,绝对不会相交。 - 一个等价类中的子串长度连续。
- 等价类的数量是
O(n) 级别的。
第三点的证明可以用一种叫做 parent tree 的树,实际上就是根据第 1 条的性质画出的树结构,并且这种结构保证状态数在
但我认为最巧妙的是后缀自动机的构造,这是一个在线的过程,看起来很不可思议,每动态插入一个字符,就相当于加入一堆新后缀,就要新接许多转移边,就会导致一系列等价类集合更新,似乎不是很能做到快速更改。
然后这里就一直不是很能理解,后来在某处看到了自动地这个词,突然领悟了一些。
我们从来没有维护过 endpos 集合的具体位置,为什么呢,因为这个集合是会自动更新的,当你进行插入时,它就已经改变了,并不需要手动修改。
然后再回过头来看插入操作,最长的那个串
如果上跳过程中存在一个位置
- 只有
p 转移到q (此时len(q)=len(p)+1 ),直接在q 的 endpos 集合中插入|S| 即可,不用进行进一步修改,那我们也只需要将新节点的link 指向 - 不只有
p 转移到q ,显然需要一个全新的等价类,我们直接新创建一个等价类clone ,很显然地,p 和从p 继续往上跳的且能转移到q 的点,都会指向新建节点clone ,因为这才是它们转移到的等价类,而原来q 所代表的 endpos 集合就成为了clone 的真子集,根据定义link 自然要指向clone ,新节点的link 也同理,由于只新增了一个字符,那么加入一个不同字符的转移一定不会有以|S| 位置为结尾的后缀,如果存在,那么一定是形如aaaaa的串,但很显然这会被情况 1 处理掉,所以q 的所有转移放在clone 上全部成立,至于clone 的len ,由于只有p 及其往上跳的部分点会转移到clone ,所以自然是len_p+1 。
那么至此插入的三种情况全部解决,只需要把
这算是我对后缀自动机的理解过程吧,状态数证明和一些细节的证明还没有深入探究,以后再继续深入。