SAM
关于后缀自动机整个算法,在这了就不多说了,感觉别人写的都很好QAQ
可以去看看参考资料里的几篇文章,感觉学过OI的应该都能看懂。。
在这里我就写一下自己的理解和一些性质和应用
我的模版
1 | void extend(int c) |
时间复杂度
状态数
状态数是$O(n)$的,这十分显然。
每个状态按照pre边连接可以变成一棵树。
父子节点集合关系是包含的
兄弟节点集合关系是无交的
所以脑补一下,就可以发现状态个数最多就是二叉树的时候,也就只有$2n-1$。。。
转移数
首先,一个十分显然的结论是,转移数最多为$O(\Sigma * n)$,其中$\Sigma$表示字符集大小
有一个更加紧的上限:
$$3n-4\; (对于n\geq 3)$$
然后,考虑以下的事实:
初始节点开始的最长的转移路径树
节点数为$2n-1$,那么边数即为$2n-2$
考虑不在这棵树中的转移$u\to v$,字符为$y$
那么初始节点到$u$的字典序最大字符串(最小应该也行,只要保证唯一)$x$,以及$v$到任意一个终止节点的最长字符串(同样也可以是最小)$z$。
$x+y+z$即为原串的一个后缀,后缀只有$n-1$个,(原串这个后缀肯定在刚开始的树上)。
因此转移数的上限即为$3n-4$
能达到上限的字符串
$$abb \dots bc$$
修改to
的次数
从上面的模版可以看出,现在除了
1 | for (; u && to[u][c] == w; u = pre[u]) |
这一段之外时间复杂度就是$O(n)$
那么,这一段的总共的时间复杂度是多少呢?
定义$minlen(u)$表示为能走到$u$的所有字符串的最小长度
显然$minlen$有一个性质:
$$minlen(u)>minlen(pre[u])>minlen(pre[pre[u]])\cdots > minlen(root) \quad (*)$$
那么考虑$minlen(pre[last])$这个东西
如果执行到了这个for
循环,那么$pre[last]$就变成了$neww$
可以发现这个for
循环如果循环了$k$次
那么$minlen(neww)\leq minlen(lastu)+1$,其中$lastu$是for
循环枚举的最后的$u$
通过$(*)$可以知道$minlen(lastu)\leq minlen(pre[last])-k$
那么$minlen(pre[neww])\leq minlen(pre[last])-k+1$
其中$+1$最多加$n$次,$minlen(pre[last])$的初值为$0$
从而for
循环执行的次数是$O(n)$的
总结
从上面三部分可知,构建后缀自动机的的时间复杂度为$O(n)$
如果有不懂的欢迎打扰
如果有哪位大佬能想出更好的证明方法的话,请务必告诉我QAQ
一些性质及应用
后缀自动机每个节点的状态为:后面还要输入哪些串就是一个后缀
对于后缀自动机中每个节点所表示的字符串(起点到这个节点的所有字符串),是在原串中出现位置一模一样的所有字符串。按照长度排序后,是长度依次减一并且是前一个的后缀。比如:$abcac, bcac, cac$
一个节点沿着
pre
一直走到起始节点的所有节点所表示的所有字符串,正好是最长的字符串的所有后缀
本质不同的字串个数
答案显然是$\sum_u len[u] - len[pre[u]]$
字符串循环移位的最小表示
求$S$的循环移位的最小表示
将$S+S$建一个后缀自动机
然后从起始节点开始贪心每次选最小的出边,选择$|S|$次后得到的字符串就是最小表示
求$right$集合
可以在每次extend
之后令size[last] = 1
,然后按照pre
边可以求出每个状态$right$集合的大小
如果只是想单纯地输出每个状态的集合的话,可以直接沿着pre
边的反向边求出$right$集合。因为前面也已经说过,走到的状态数最多只有$2|right|-1$个
当然也可以利用线段树合并搞出每个状态具体的$right$集合,这样就会多一个$log$
用拓扑排序的写法写起来比较简单,也可以用DFS
求两个串的最长公共子串
求$S$和$T$的最长公共子串
可以先对$S$建一个后缀自动机
然后拿$T$在自动机上跑,如果有转移则转,没有转移就沿着pre
向上跳(话说这不是自动机的基本操作吗。。。)
每次转移后拿len
更新答案
推广:广义后缀自动机
说是广义,其实就是建多个串$S_i$的后缀自动机。。。
那么应该怎么建呢?
一种十分简单粗暴的方法就是将$S_1+’ #’+S_2+’ #’+\cdots +’ #’+S_k$建后缀自动机
还有一种方法,就是将原来的模版改成这样:
1 | void extend(int c) |
每次添加完一个字符串后将last
赋为root
就行了