SAM 习题题解

不多说了,直接看题吧。

P3804 【模板】后缀自动机(SAM)

模板题,在 Parent 树上树形 DP 即可。

代码

P3975 [TJOI2015] 弦论

显然,我们可以直接 DAG 上 DP + 试填法。

如果 \(t = 0\),那么万事大吉。但是我们有万恶的 \(t = 1\),所以还得处理一下每个等价类的大小。

这题是真恶心,先是 WA 了所有 \(t = 0\),然后搞定 \(t = 0\) 后又 WA 了所有 \(t = 1\),哎。

代码

SP1811 LCS - Longest Common Substring

\(S\) 建出 SAM,在 \(S\) 中匹配 \(T\),失配则跳后缀链接,最后取 \(\max\) 即可。

先粘个 SA 代码,SAM 代码以后再补。

代码

P6640 [BJOI2020] 封印

考虑和上一题一样的思路,在 \(t\) 中匹配 \(s\),你会发现你可以求出 \(s\) 的每个前缀在 \(t\) 中的最长后缀,记为 \(f_i\)

然后你就会发现答案形如:

\[\max_{i = l} ^ r \min \{ f_i, i - l + 1 \} \]

直接二分答案即可。

我实在不想写万恶的 SAM 了,所以这题也用 SA 了。

代码

CF1063F String Journey

首先,\(t\) 的长度一定是 \(k, k-1, \cdots 3,2,1\)。证明略。

\(f_i\) 表示只考虑区间 \([i, n]\) 表示的字符串时,选取的最后一个字符串必须包含 \(i\) 时,最大的 \(k\)。那么对于转移点 \(j\),我们只需要判断 \(s_{[i, i + f_j - 1]} = s_{[j, j + f_j - 1]}\) 或者 \(s_{[i + 1, i + f_j]} = s_{[j, j + f_j - 1]}\) 即可。

考虑优化这个过程,注意到随着 \(i\) 的减小,\(i + f_i - 1\) 是不增的。这启发我们双指针枚举 \(i\)\(i + f_i - 1\),枚举时判断这个区间是否合法。具体地,记我们枚举到的区间为 \([i, r]\)(也就是 \(r = i + f_i - 1\)),我们只需要判断是否存在一个 \(j \in [r + 1, n]\),满足:

  • \(f_j \geq r - i\)。这是因为我们最后一个字符串的长度选取的是 \(r - i + 1\),那么上一个就是 \(r - i\)\(f_j\) 必须要大于等于这个值。
  • \(s_{[i, r - 1]} = s_{[j, j + r - i - 1]}\) 或者 \(s_{[i + 1, r]} = s_{[j, j + r - i - 1]}\)

第一个条件比较容易刻画,线段树维护最大值即可。对于第二个条件,一个经典转化是将这种问题转化成 LCP(最长公共前缀的长度)的关系。具体地,它等价于 \(\text{LCP}(\text{suf}(i), \text{suf}(j)) \geq r - i\) 或者 \(\text{LCP}(\text{suf}(i + 1), \text{suf}(j)) \geq r - i\)。这可以在 Height 数组上二分求出 \(j\) 的排名区间,进而得到满足条件的 \(j\)

时间复杂度 \(O(n \log n)\),被线性做法完爆了,但是起码比 \(O(n \sqrt{n})\) 的枚举答案 + Hash 好。

代码

CF700E Cool Slogans

首先,\(s_{i-1}\) 一定是 \(s_i\) 的一个后缀。那么答案就是 Parent 树中一条返祖链上的若干个点。

我们先钦定选了 \(u\),考虑在它的祖先节点中选取。显然,我们如果要选择一个等价类,那么我们只能选取这个等价类中的一个字符串,同时选任何一个都不会改变它和 \(u\) 的合法性。那么选最长的那个肯定是不劣的。

有了上面那几个性质这个题就好做多了。对于每个等价类,我们可以忽略短串,只考虑最长的串。

考虑一个树形 DP,设 \(f_u\) 表示考虑根节点到 \(u\) 的链中,可以选取最多的字符串(节点)数。\(g_u\) 表示最后选取的节点。

考虑转移的最优性,这里有一个小贪心,就是“能选就选”的原则。也就是说,如何我们能选取一个节点,那么必然要选取它。证明可以使用调整法,如果没选它,那么将后面的一个节点调整为它也是合法的。

有了这个贪心以后,我们就可以放心地 DP 了。考虑从 \(u\) 转移到它的子节点 \(v\),如果 \(g_u\)\(v\) 是合法的,即 \(g_u\)\(v\) 中有一次不是以后缀的形式出现,有:

\[f_v = f_u + 1, g_v = g_u \]

否则,有:

\[f_v = f_u, g_v = v \]

如何判断 \(g_u\)\(v\) 中有一次不是以后缀的形式出现呢?我们需要维护节点的 endpos 集合,记为 \(\text{endpos}(i)\)。还需要维护节点的任意一个结束位置,记为 \(p_i\)。最后还有最长串的长度,记为 \(l_i\)。那么,只需满足 \(\text{endpos}(g_u) \cap [p_v - l_v + l_{g_u}, p_v - 1] \neq \varnothing\) 即可。endpos 集合可以使用可持久化线段树合并维护,时间复杂度 \(O(n \log n)\)

建 SAM 复制节点时一定要复制结束位置,否则你会 WA on #10。可持久化时一定要完全可持久化,否则你会 WA on #11。血的教训。

代码

posted @ 2025-03-09 20:29  Eliauk_FP  阅读(47)  评论(2)    收藏  举报