金华一中杂题选讲
A [BalticOI 2024] Jobs
\(\text{Link}\)
考虑一个 dp,设 \(f(x)\) 表示为了在 \(x\) 子树内挣到钱,需要的最少的本金。先考虑怎样求答案,我们从根节点出发开始扩展,每次取出当前 \(f(x)\) 最小的值,显然这样一定更优。然后我们加上 \(x\) 的贡献,并把 \(x\) 的所有儿子扩展出去。直到当前取出的最小 \(f(x)\) 大于当前总钱数,说明无法再赚钱了,此时的总钱数减去初始钱数即为利润。
显然上述过程可以用堆维护,复杂度 \(O(n\log n)\)。考虑怎样求 \(f(x)\)。
首先我们可以仿照上面的方法去求,假设当前的总钱数为 \(now\),如果某一次扩展完 \(now>f(x)\) 说明我们赚到钱了,可以退出;否则我们就要继续扩展赚钱,与上面不一样的是,如果此时取出的最小 \(f(i)>now\),我们需要抬高本金,即将 \(f(x)\) 加上 \(f(i)-now\),使得 \(now\) 变为 \(f(i)\) 后再去赚钱。
不难发现这样做的复杂度最坏是 \(O(n^2\log n)\) 的,考虑优化。不难发现一点:如果我们扩展出来了 \(f(i)\) 并且决定选,那么我们一定要在这个子树中赚钱;而在 \(i\) 子树中赚钱的流程和之前求 \(f(i)\) 的流程是完全一致的,也就是说我们可以保留每一次求出 \(f(i)\) 后堆里面剩下的元素,如果扩展到 \(f(i)\) 就直接跳过中间的流程来到这里,并且 \(now\) 直接加上中间赚的钱数。用可并堆维护一下即可,复杂度 \(O(n\log^2 n)\)。
B [BalticOI 2022] Uplifting Excursion
\(\text{Link}\)
这个题显然是一个背包问题,但是 \(l\) 比较巨大,无法按照传统方法做。
考虑一个经典的结论:我们优先放性价比最高的若干个物品,将体积调整到 \([l-m,l]\),然后再进一步调整到 \(l\)。在最后调整的过程中体积变化量是不超过 \(m^2\) 的。证明的话首先考虑,我们的体积最开始在 \([l-m,l]\),调整过程中一定有方案使得 \(S\in [l-m,l+m]\)。而这中间的每个值我们都只会经过一次,如果经过两次还更优的话我们在第一步贪心的时候就应该算上了。所以每个物品最多选 \(m\) 次,体积变化在 \([-m^2,m^2]\) 内。
于是我们只需要跑一个体积为 \(m^2\) 的多重背包即可,这样的复杂度就可以承受了。使用二进制分组优化后的复杂度为 \(O(m^3 \log m^2)\)。
C [CF771E] Bear and Rectangle Strips
\(\text{Link}\)
首先有一个比较简单的 \(O(n^2)\) dp:设 \(dp(i,j)\) 表示第一行考虑到 \(i\) 列,第二行考虑到 \(j\) 列的最大矩形数。转移是比较容易的,提前预处理出每一个点向右合法的第一个扩展点即可做到 \(O(1)\) 转移。不过枚举的状态过多,无法通过。
考虑怎样优化状态数,我们有一个直觉是我们希望两行选的矩形尽可能均匀些,也就是说 \(i,j\) 不要相差过大。考虑先关注 \(dp(i, i)\),接下来我们断言:只有满足 \(dp(i,j)=dp(i,i)+1\) 或 \(dp(j,i)=dp(i,i)+1\) 的第一个 \(j(j\ge i)\) 是重要的,即我们只需要对它们向后转移即可。
感性理解起来非常容易,这个要求相当于每次只扩展一个矩形,显然扩展多个的情况是可以由扩展一个的情况推出的,这样的话就可以节省时间复杂度。显然每个 dp 状态的后继是 \(O(1)\) 个,最开始只有一种状态,所以总状态数是 \(O(n)\) 的。
D [CF1975G] Zimpha Fan Club
\(\text{Link}\)
首先如果没有 *
此题是简单的,考虑怎样搞掉这个东西。不妨进行分类讨论。
发现如果两边都没有 *
就是平凡的情况,而如果都有其实也很平凡,这要求两个字符串第一个和最后一个 *
的左边和右边能够匹配,手玩后不难发现一定存在方案使得中间可以匹配上。
现在的问题就是怎样求只有一个字符串有 *
的问题了,首先还是先判断两边字符能否匹配,这样我们的第一个串就变成 *S*S*S*S*S*S*
这样的形式了。考虑我们实际上只需要求出每一个小的 \(S_i\) 串能否在 \(T\) 中匹配,如果可以就向后转移。而带通配符的字符串匹配可以使用多项式卷积来求,复杂度是 \(O(n^2 \log n)\) 的。
考虑这个做法不优在哪,我们每次都拿整个 \(T\) 串和 \(S_i\) 去匹配,实际上这不必要。我们只需要取当前的前 \(2|S|\) 个字符匹配,如果匹配失败那么就删掉前 \(|S|\) 个字符即可。这样我们就可以单次在 \(O(|S|\log |S|)\) 的复杂度内删掉至少 \(|S|\) 个字符,复杂度就是 \(O(n\log n)\) 的了。
不过此题的 \(n\) 有 \(2\times 10^6\),有点卡常,几个方法如下:
- 先全部转成点值表示,算完后再转回系数表示,这样可以少做两次 NTT。
- 处理 \(S_i\) 的系数时可以提前预处理。
H [CF1270G] Subset with Zero Sum
\(\text{Link}\)
本题单里唯二做出来的题之一。
考虑 \(i-n\le a_i\le i-1\) 实际上相当于 \(1\le i-a_i\le n\)。考虑连边 \(i\to i-a_i\),这样每个点出度为 \(1\),必然构成一棵基环树。而基环树上的环正好满足每个点编号之和等于每个点指向的编号之和,即 \(\sum i=\sum i-a_i\),于是环上的点的 \(\sum a_i=0\),满足要求。
只需要实现基环树找环即可,复杂度 \(O(n)\)。