不知道 SA 是什么?SA 的基础知识
以下所有题目都可以用 SAM 解决,这里记录 SA 做法。
SPOJ LCS2 - Longest Common Substring II
题目链接:https://www.spoj.com/problems/LCS2/
题意
给定不超过 $10$ 个长度不超过 $10^5$ 的字符串,求它们的最长公共子串长度。
做法
子串就是某个后缀的前缀。把所有字符串中间加上分隔符连成一个求 SA,最长公共子串的长度就是从原先的每个字符串挑一个后缀出来求 LCP,也就是 $height$ 数组的区间求 $\min$。
考虑排名数组上的每一个区间,这个区间满足条件,当且仅当原先的每个字符串都有一个后缀在这里面。把原先的字符串当成颜色,对应后缀染色,区间满足条件即为这里面每种颜色都出现了。可以滑动窗口求所有满足条件的区间,单调队列维护区间 $\min$。时间复杂度 $O(n\log n)$。
在实现的时候,字符串连在一起时加上分隔符可以规避一些奇怪的特殊情况。
代码:
1 | // code by RabbieWjy |
Luogu P2408 不同子串个数
题目链接:https://www.luogu.com.cn/problem/P2408
题意
给你一个长为 $n$ 的字符串,求本质不同的子串的个数。
$1 \le n \le 10^5$。
做法
先求出 SA 和 $height$ 数组。总共的 $\frac{n(n + 1)}{2}$ 个子串中,对于每个后缀,它和前一名的 LCP 都是多算了的,要减去,也就是总共减去 $\sum \limits _{i=2} ^n height[i]$。
也许可以直接记结论?
代码简单,就不放了。
[NOI2018] 你的名字
题目链接:https://www.luogu.com.cn/problem/P4770 / https://loj.ac/p/2720
题意
给定字符串 $S$,$Q$ 次询问,每次询问给出字符串 $T$ 和区间 $[L,R]$,求有多少个 $T$ 的本质不同子串不是 $S_LS_{L + 1}\cdots S_R$ 的子串。
$|S| \le 5 \times 10^5$,$Q \le 10^5$,$\sum |T| \le 10^6$。
做法
符号:$S(L,R)$ 表示字符串 $S_LS_{L+1}\cdots S_R$,字符串 $T$ 的后缀 $i$ 表示字符串 $T_iT_{i + 1}\cdots T_{|T|}$。
和上面一道题类似,考虑从所有子串中减去算多的。
先把所有字符串加上分隔符连起来。所有本质不同子串个数就是上一题,现在需要把这些子串中是 $S(L,R)$ 的子串的减掉。
对于每个字符串 $T$ 的后缀 $i$,存在一个最长的前缀,使得它是 $S(L,R)$ 的子串,且比这个前缀长的前缀都不是 $S(L,R)$ 的子串。记这个前缀长度为 $len_i$,则有 $len_{i + 1} \ge \max(0,len_i - 1)$。证明类似 $height$ 数组的结论证明,具体来说就是 $T(i + 1,i + len_i - 1)$ 就是 $T(i,i + len_i - 1)$ 去掉第一个字符,显然也是 $S(L,R)$ 的子串。
根据这个结论,我们可以类似求 $height$ 数组的过程求出 $len$ 数组。但是,这里面需要快速判断 $T(i,i + X - 1)$ 是不是 $S(L,R)$ 的子串。这个判断相当于判断是否存在 $j \in [L,R - X + 1]$,使得 $\operatorname{LCP}(S(j,j + X - 1),T(i,i + X - 1)) = X$,也就是 $\operatorname{LCP}(S(j,|S|),T(i,|T|) \ge X$。对于一个固定的 $i$,$S(j,|S|)$ 的排名在一个包含 $rank[T(i,|T|)]$ 且区间 $height_{\min} \ge X$ 的区间里,可以二分+ST 表找出这个区间。判断就转化为了区间中是否存在 $j\in [L,R - X + 1]$ 的问题,可以通过主席树解决。
求出来 $len$ 以后,就可以把答案算出来了。注意减掉是 $S(L,R)$ 的子串个数的时候,有一些已经在求本质不同子串的时候减掉了的子串要加回来。
总时间复杂度是 $O(n\log n)$,$n=|S| + \sum |T|$。我的代码常数较大。
代码:
1 | // code by RabbieWjy |
[美团杯2020] 半前缀计数
题目链接:https://uoj.ac/problem/523
题意
定义一个字符串的半前缀为一个前缀(可以为空)删去它的一个子串(可以为空)的结果。求字符串 $S$ 有多少个本质不同的半前缀。
$1 \le |S| \le 10^6$。
做法
半前缀的定义相当于一个前缀加上后面的某个子串。对于每一种半前缀,存在一个最大的 $i$,使得它由 $S(1,i)$ 加上后面的某个子串构成,且无法被 $S(1,j)$($j > i$)和后面的某个子串构成。考虑对于每一个前缀 $S(1,i)$,计算它作为最大满足上述条件的 $i$ 的半前缀数量。
把后面加的子串记为 $T$。由于 $i$ 是最大的,$T_1 \ne S_{i + 1}$,否则可以选 $S(1,i + 1)$ 和 $T$ 去掉第一个字符拼起来。发现如果 $T_1 \ne S_{i + 1}$,则 $i$ 一定是最大的。因此只需要计算 $T_1 \ne S_{i + 1}$ 的本质不同串数量。
求出 $S$ 的 SA,由于 $T$ 要是 $S(1,i)$ 后面的某个子串,考虑从后往前求答案。可以用上两道题的结论,求出 $S(i + 1,|S|)$ 的本质不同子串数和分别以每个字母开头的本质不同子串数,然后直接求答案。中间需要维护后缀 $i + 1$ 到 $|S|$ 的按排名排序后两两相邻的 $\operatorname{LCP}$,可以用数据结构(如树状数组)快速求出当前新加入排名的前驱和后继,加上 ST 表更新答案。总时间复杂度 $O(n\log n)$,$n = |S|$。
代码:
1 | // code by RabbieWjy |
更新中……