ARC058F - Iroha Loves Strings 题解

这是 NOI 班做出的第一道题目,祭一个。

wdnmd 这是个 Au 题/jk/jk


Portal

首先有个很显然的 DP。\(dp_{i,j}\) 表示考虑到第 \(i\) 个串,能连接起来的长度为 \(j\) 的字典序最小的串。那么这样状态数是 \(\mathrm O(nm)\) 的,然后因为要存串,所以时空都至少是 \(\mathrm O\!\left(nm^2\right)\)。考虑优化之。

注意到字典序的一个特性(这也是一个常用的套路):它不像其他的值,比如说加法,你前面的加数小一点没关系,后面可能再追回来;而字典序比较过程中,从前往后一旦找到一个不同的字符,那么只需要比较它们即可,这位大的后面再小也永无翻身的机会。

对于同一个 \(i\),若对于 \(j<k\)\(dp_{i,j}\) 不是 \(dp_{i,k}\) 的前缀,那么它们已经决出高下,以后后面接什么都没有任何关系了,它们的大小已经固定了;而如果是前缀,则还要看后面接的串表现怎么样。那假如说 \(dp_{i,j}\)\(dp_{i,k}\) 后面都至少有一种方案接成长度为 \(m\) 的串,那么可以说 \(dp_{i,j}\)\(dp_{i,k}\) 中较大的那个卵用已经没有了,之后的 DP 值只要转移到它,则没有任何希望成为答案的前缀。注意这里需要它们有可能补成长度为 \(m\),也就是后面的串长度们有选择的方案使得和为 \(m-j/k\)。这个是否有方案只需要从后往前 01 背包 \(\mathrm O(nm)\) 即可。

我们首先将之后没有机会的 DP 值标记成无效,它们不参与 DP 过程。然后剩下来的都是有机会的,根据上面的结论,所有有卵用的 DP 值从短到长排序后,一定是一个是下一个的前缀这样子。我们认为只有这些有卵用的才是有效的,其他全部标记成无效。那么这样下来,我们可以对于每个 \(i\) 搞一个母串,每个 DP 值都是这个母串的一个前缀,只需要用一个数表示,并且母串只有一个,这样空间就搞成 \(\mathrm O(nm)\) 了,并且时间也很有希望优化的 Ar 子。

然后考虑怎么决定出每个 DP 值是否有效,以及母串就是最大的那个有效 DP 值。考虑按 \(j\) 从小到大 DP,实时维护一个当前所有有效 DP 值的 \(j\) 的栈,栈的顶端就是当前的母串。\(dp_{i,j}\) 的决策只有两个,一个是 \(dp_{i-1,j}\),一个是 \(dp_{i-1,j-|a_i|}+a_i\),比个大小算出一下 \(dp_{i,j}\)。然后分三种情况:

  1. 当前母串是 \(dp_{i,j}\) 的前缀。那万事大吉,dark 保持前面那些有效 DP 值不动,将 \(dp_{i,j}\) 压入栈并令为新母串;
  2. 当前母串小于 \(dp_{i,j}\)。那 \(dp_{i,j}\) 滚蛋吧,你已经卵用没有了;
  3. 当前母串大于 \(dp_{i,j}\)。那当前母串危,\(dp_{i,j}\) 要取代你的位置了。于是我们一直弹出,直到栈顶为 \(dp_{i,j}\) 的前缀,然后实施 \(1\)。这个栈可以看作一个关于字典序的单调栈(它的确是单调的),于是这个操作的正确性也就不言而喻、一目了然了。

注意到因为这是个单调栈,还有一些其他线性次数的操作,所以比较字符串的次数一共是 \(\mathrm O(nm)\) 的,每轮 \(\mathrm O(m)\),我们必须要快速比较。每轮分别考虑:显然比较对象只可能是两种形式:上一轮的母串的前缀或上一轮的母串的前缀连上 \(a_i\)。想要比较字典序大小,难点在于找到第一个不同的位置。可以预处理 \(a_i\) 和母串的前缀哈希值,然后二分,这样是 \(\mathrm O(nm\log)\) 的;然后不难发现,母串的前缀和母串的前缀肯定是前缀关系,没有不相同的位置,所以我们只需要找 \(a_i\) 与母串的一个后缀、\(a_i\)\(a_i\) 的一个后缀的第一个不相同位置,那这不就是 Z 算法所能做的事情吗(爷青回)?复杂度 \(\mathrm O(nm+\sum|a_i|)\)

code

posted @ 2020-11-14 19:10  ycx060617  阅读(308)  评论(0编辑  收藏  举报