SAM 应用
文章目录
关于本文中的符号的定义,参见《后缀自动机与后缀树》一文的摘要。
A 简单应用
两个后缀 LCP
后缀树
就是两个结点在 fail 树上的 LCA。
本质不同的子串个数
后缀树
设表示等价类中最长的字符串的长度,也就是通常称的。则显然。因此的本质不同子串个数为
字典序第 k 小子串
SAM
多组询问,查询的子串中字典序第小的子串。
遇到字典序最小的问题,就不太能用后缀树求解。因此我们直接利用,在 SAM 上 DFS 一遍即可。
复杂度。
匹配同构串
询问在串中出现了多少次的循环同构串。
SAM
首先对建 SAM,然后先在 SAM 上跑一遍。再接着跑一遍,这次就可以记录答案了。我们记录一个当前匹配的长度,对的长度取 min。这样我们就可以判断当前结点包含的状态中是否有的循环同构串。有一个贪心的想法就是,每次走了一个字符,就贪心地跳 fail,要求 fail 的长度也大于等于当前匹配长度。因为 fail 的转移指针是包含它的儿子结点的。然后加上当前状态的出现次数即可。
B 进阶套路
B1
设为所有长度为的子串的出现次数的最大值,求。。
SAM 可以算出每个子串的出现次数。对于结点,相当于区间的答案对取。
事实上没有这么复杂。由于,因此可以把区间左端点置为,也就是个前缀。扫一遍即可。
时间复杂度。
B2
维护串,支持
- 在末端添加一个字符
- 询问在中的出现次数。
。
显然,我们需要动态维护 Fail 树,支持:
- 添加一个叶子结点 / 中间多一个结点
- 查询子树和
可以直接 LCT。但这题动态性较弱,可以平衡树维护 DFS 序来做。
离线的话直接把整个 SAM 建出来,然后询问离线搞一下就行。
B3
给定串。对于串,定义它的权值是至少需要几个的子串拼起来得到。拼不出来就是。求长度为为的权值最大的串的权值。
。
对于一个串,如何计算它的权值?
它满足贪心条件,我们贪心地匹配的子串直到不能匹配,就断开。
假设分成段,那么有。
于是我们设表示最短的串使得的长度。显然可以瞎搞搞出来。
然后用它二分 + 矩乘即可得到答案。这是双 log 的。
把二分改成倍增就是单 log。
B4
给定字符串,对于每个,求把变成
#
后,本质不同的的子串的个数。。
容斥,我们要求的就是的本质不同数,的本质不同数,的公共本质不同子串数。前两者可以正着倒着建 SAM 求出。
对于后者,我们考虑每个 SAM 结点的贡献。
对于每个结点,我们求出它出现的最左边和最右边的位置,分别记为。
那么对于,区间的答案就会加。
差分一下,就变成了,的加,的减。
二次差分就变成纯单点了。
这样,总复杂度就是的。
B5 线段树维护 Right 集合
所谓的 Right 集合是指一个状态在原串中所有出现的位置的集合。
与求子串出现次数类似,我们在构建 SAM 的时候每次在 last 结点的集合里插入一个位置。然后建完 SAM 后线段树合并一下即可。
C
HEOI2016 字符串
给定字符串和个询问,每个问题形如四个参数,询问的所有子串和的 LCP 的长度的最大值。
。
首先把串翻转,问题转化为的子串与的 LCS 的长度的最大值。
也就是说我们想知道的最长的被包含在中的后缀。二分转化为判定问题。判定问题可以使用线段树维护 Right 集合来解决。
时间复杂度。
参考文献
OIWIKI. 后缀自动机 (SAM). https://oi-wiki.org/string/sam/
修订记录
- 2021年2月11日 第2次修订
- 2020年7月23日 创建文章