关于本文中的符号的定义,参见《后缀自动机与后缀树》一文的摘要。

A 简单应用

两个后缀 LCP

后缀树

就是两个结点在 fail 树上的 LCA。

本质不同的子串个数

后缀树

表示等价类中最长的字符串的长度,也就是通常称的。则显然。因此的本质不同子串个数为

字典序第 k 小子串

SAM

多组询问,查询的子串中字典序第小的子串。

遇到字典序最小的问题,就不太能用后缀树求解。因此我们直接利用,在 SAM 上 DFS 一遍即可。

复杂度

匹配同构串

询问在串中出现了多少次的循环同构串。

SAM

首先对建 SAM,然后先在 SAM 上跑一遍。再接着跑一遍,这次就可以记录答案了。我们记录一个当前匹配的长度,对的长度取 min。这样我们就可以判断当前结点包含的状态中是否有的循环同构串。有一个贪心的想法就是,每次走了一个字符,就贪心地跳 fail,要求 fail 的长度也大于等于当前匹配长度。因为 fail 的转移指针是包含它的儿子结点的。然后加上当前状态的出现次数即可。

CF 234C

B 进阶套路

B1

为所有长度为的子串的出现次数的最大值,求

SAM 可以算出每个子串的出现次数。对于结点,相当于区间的答案对

事实上没有这么复杂。由于,因此可以把区间左端点置为,也就是个前缀。扫一遍即可。

时间复杂度

B2

维护串,支持

  1. 在末端添加一个字符
  2. 询问中的出现次数。

显然,我们需要动态维护 Fail 树,支持:

  1. 添加一个叶子结点 / 中间多一个结点
  2. 查询子树和

可以直接 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/