可爱的串串题 Ciallo~(∠・ω< )⌒★
1. CF1063F String Journey(SA,DP)
题目。
首先,\(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 好。
代码。
2. CF700E Cool Slogans(SAM,DP,可持久化线段树合并)
题目。
首先,\(s_{i-1}\) 一定是 \(s_i\) 的一个后缀。那么答案就是 Parent 树中一条返祖链上的若干个点。
我们先钦定选了 \(u\),考虑在它的祖先节点中选取。显然,我们如果要选择一个等价类,那么我们只能选取这个等价类中的一个字符串,同时选任何一个都不会改变它和 \(u\) 的合法性。那么选最长的那个肯定是不劣的。
有了上面那几个性质这个题就好做多了。对于每个等价类,我们可以忽略短串,只考虑最长的串。
考虑一个树形 DP,设 \(f_u\) 表示考虑根节点到 \(u\) 的链中,可以选取最多的字符串(节点)数。\(g_u\) 表示最后选取的节点。
考虑转移的最优性,这里有一个小贪心,就是“能选就选”的原则。也就是说,如何我们能选取一个节点,那么必然要选取它。证明可以使用调整法,如果没选它,那么将后面的一个节点调整为它也是合法的。
有了这个贪心以后,我们就可以放心地 DP 了。考虑从 \(u\) 转移到它的子节点 \(v\),如果 \(g_u\) 和 \(v\) 是合法的,即 \(g_u\) 在 \(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。血的教训。
代码。
3. P4022 [CTSC2012] 熟悉的文章(广义 SAM,单调队列优化 DP)
题目。
显然要二分答案,下面我们讨论 check 怎么做。
首先,由于是多个主串,所以要建出它们的广义 SAM。考虑经典套路,预处理 \(l_i\) 表示询问串的前缀 \(i\) 中最长出现过的后缀的长度。这显然是可以线性做的,在此不多赘述。接下来,由于是连续段问题,考虑 DP。设 \(f_i\) 表示只考虑询问串的前缀 \(i\) 时划分的最长总长度。显然,有转移方程:
注意到 \(i - l_i\) 不减,所以单调队列优化即可。时间复杂度 \(O(n \log n)\)。
代码。
4. P4770 [NOI2018] 你的名字(SAM)
题目。
还是经典套路,考虑求出 \(T\) 的每个前缀 \(i\) 在 \(S_{[l, r]}\) 中的最长匹配后缀的长度 \(f_i\)。怎么求我们待会再说。由于与 \(T\) 的本质不同子串数有关,所以我们需要建出 \(T\) 的后缀自动机。对于后缀自动机上的一个节点 \(u\),我们记 \(\text{mxl}_u\) 表示这个 Endpos 等价类的最长串,\(\text{mnl}_u\) 表示这个 Endpos 等价类的最长串,\(\text{pos}_u\) 表示这个 Endpos 等价类的任意一个 Endpos。这样,对于节点 \(u\) 来说,有以下几种情况:
- \(f_{\text{pos}_u} < \text{mnl}_u\),此时 \(u\) 中所有串均满足条件,\(u\) 的贡献为 \(\text{mxl}_u - \text{mnl}_u + 1\)。
- \(\text{mnl}_u \leq f_{\text{pos}_u} \leq \text{mxl}_u\),此时 \(u\) 中有一部分串满足条件,\(u\) 的贡献为 \(\text{mxl}_u - f_{\text{pos}_u}\)。
- \(\text{mxl}_u < f_{\text{pos}_u}\),此时 \(u\) 中所有串均不满足条件,\(u\) 的贡献为 \(0\)。
答案即为所有节点的贡献之和。
现在,我们需要求出 \(f_i\)。由于有 \([l, r]\) 的限制,普通求法肯定是不行的。考虑仿照普通求法,把 \(T\) 放在 \(S\) 的后缀自动机上跑,失配则跳 Link(这里的失配需要拿 Endpos 集合来判)。显然,选取区间 \([l, r]\) 内最靠右的 Endpos 是最优的。需要注意的是,匹配长度有 \(l\) 的限制。具体地,设当前节点为 \(u\),它在区间 \([l, r]\) 内最靠右的 Endpos 为 \(x\),那么我们的匹配长度实际上是 \(\min(\text{mxl}_u, x - l + 1)\)。其他的就与普通做法一样了。跳到一个可以匹配的节点,更新答案,然后退出。但是这样交上去还是会 WA,为什么呢?这是因为一条 Link 链上 \(\min(\text{mxl}_u, x - l + 1)\) 的值不一定是单调的。那么怎么办?其实我们只需要一直往上跳 Link,直到匹配时不受 \(l\) 的限制,即 \(x - l + 1 \geq \text{mxl}_u\) 即可。
时间复杂度玄学,反正能过。
代码。

浙公网安备 33010602011771号