后缀自动机(SAM)学习小记

来源:互联网 发布:闪讯网络电缆没有连接 编辑:程序博客网 时间:2024/05/17 22:11

Text

自动机的概念此处不解释。。

引入

我们需要一种数据结构能够识别一个字符串S的所有后缀

一种方法是直接建一棵Trie树,把每个后缀扔到里面去

这样的时空复杂度都是O(N^2)的,不能满足我们的要求

SAM应运而生

观察建好的Trie树,我们发现有很多的状态是冗余的,利用率低下

核心思想

SAM的思想就是将同一类的状态缩到一个点上
这样原来的Trie就变成了一个DAG

定义集合Right表示某一个节点所代表的字符串在S中的出现位置的右端点的集合

对于Trie上某一个节点,它所代表的字符串是唯一的,是S的某一个子串。

对于某一个节点记录一个len,表示根到这个点的距离,也就是所代表的串的长度。
那么只要有了Right集和len,我们就能获得这个字符串了
就是Right集中的位置向左len个长度

为了方便理解,设Right集合中的元素为w1~wm
那么S[w1len+1...w1]=S[w2len+1...w2]=......,这就是它所代表的字符串

我们尝试合并状态,尝试将Trie上所有Right集相同的点缩到一起

那么合并以后,一个节点所代表的字符串变成了多个,一个节点的len变成了一个区间,记为[min,max]

这个节点所代表的字符串就是Right集中的位置向左[min,max]这个区间长度所得到的字符串集合

定义fail指针表示Right集包含当前节点的Right集,并且其最小的位置

设当前点为p
那么显然有性质,fail[p]所代表的字符串一定都是p所代表的字符串的后缀
并且minp=maxfail[p]+1
所以实际上我们并不需要记录min

画个图很容易发现这是正确的
可以发现fail指针构成了一棵树

合并后的东西就是构造出来的SAM了

SAM的性质

首先显而易见的,这个SAM不仅能识别S串所有的后缀,同时也能识别所有的子串,并且相同的子串不会重复出现

对SAM进行DFS,走过的边组成的字符串就是S的子串,每种走法唯一对应一个子串

其次,节点之间的Right集要么一个是另一个的真子集(在fail树上的祖先),要么就不相交

如果Right集合是相交的,那么一个串必定是另一个串的后缀,那么一定成真子集关系

第三,它的转移边数和节点数都是O(|S|)的

放到fail树上考虑

很明显,因为要么不相交,要么是真子集,那么最坏情况就是叶子节点的Right集只有一个元素,然后每增加一个节点就合并一些

显而易见最多只会有2|S|个节点

同时转移边数也是O(|S|)的

此处不给出证明,有兴趣者可以自行查找。。。

SAM的构造

现在的问题是如何在O(|S|)的复杂度内将这个SAM构造出来

考虑已经构造出了S的一个前缀S1,这个前缀结尾位置为L,的SAM,然后在后面新加入一个字符设为c,并在SAM上加入相应的边和点

假设之前走到的最末的一个点为last,最末指的是有一条路径到达last,使得这条路径的字符串对应了前缀S1,显然Rightlast={L}

我们需要对所有Right集合中有L这个位置的点进行修改,因为这些点可以走一条c的边进入我们新的状态。

Right集合有L这个位置,那就是last的fail树上到根的路径咯

新加一个点np

那么从last开始在fail指针上跑,如果跑到的点没有c的出边,那就加上,指向np(直到跑到根)

如果当前点(设为p)有一个c的出边了,指向q

那么是不是应该将np的fail指向q呢

不一定

举个例子

S串为AAAAcAAAc

p所代表的最长的串是AAA,即maxp=3

分情况讨论

  • maxp+1=maxq,也就是说q代表的最长的串是AAAc,q最多只能从p过来,没有比p过来更长的了,此时发现q的Right集真包含了np的Right集,并且显然Rightq是最小的
    那么failp=q

  • maxp+1<maxq,q代表的最长的串可能是AAAAc,发现它的Right集和np没半毛钱关系
    此时可以新建节点nq,将q拆成两部分,一部分就是nq,maxnq=maxp+1,一部分就是剩下的q,max不变,minq=maxnq+1
    实际意义就是将长度小于等于AAAc切出来给nq,把长度大于它的留给原来的q
    nq最长代表AAAc,q最短代表AAAAc
    那么显然failq=nq,可以发现此时nq和第一条一样了,那么failnp=nq
    同时nq的出边、fail全部复制q的,这也显而易见
    然后可以发现现在的p已经不能走到q了,而是走到nq
    那么从p开始,沿着p的fail链走,如果c的出边是q的改成nq,直到找到第一个不是的。(上面的肯定都不是了)或者已经到根

那么就构造完毕了

其他的复杂度显然,只需要考虑在fail上跳的复杂度

记住结论就好了,总复杂度仍然是O(N)的
此处不给出证明,有兴趣者可以自行查找其实是博主太弱不会势能分析。。。

模板的话,可以参考http://blog.csdn.net/hzj1054689699/article/details/78743488

广义SAM

就是可以多个字符串的SAM合并成一个

首先将这个这些字符串建成Trie,然后在Trie上BFS,按照这个的顺序向SAM里加,last相应的是Trie上的父亲

为啥不能DFS?好像是说势能分析的时候会有问题,会被卡成N^2

应用

因为SAM可以识别S的所有字串,所以。。。

最基础的就是求两个串的最长公共子串,这是O(N)的
直接将其中一个建好SAM,用另一个在上面跑,匹配失败就沿着fail跳,当前最长长度就是当前点的max

还可以在SAM上DP,fail树上DP什么的。。。

原创粉丝点击