动态规划练习笔记
P4870 [BalticOI 2009] 甲虫 (Day1)
本题所使用到的技巧:
- 费用前置,将纵向贡献转变为横向贡献。
- 注意到不合法状态的不优性以达到忽略不合法状态来减小转移难度的目的。
很容易注意到,本题中甲虫所获得的露水一定是一段连续的区间,再注意到数据范围,很容易联想到区间 DP。套路的,我们设计 \(f_{i,j,0/1}\) 代表 \([i,j]\) 区间内的露水全喝,而甲虫现在在 \(i/j\) 可以达到的最大贡献。
注意到我们需要维护的东西有时间和贡献两部分,而你以任何一种维度作为转移对象并用另一维度辅助转移都是错误的,就像多元函数的最大值,你无法保证在某一元达到最大的情况下,保证全局的最优性,只能企图找到一个平衡点,才能取到最大值。
技巧 1:
我们只能企图另想办法。我们注意到水滴的地位都是对等的。而消耗的时间是具有传递性的。这点如何理解,其实就是说比如甲虫喝了 \(k\) 个位置的露水,那么比如我在第 \(i-1\) 个露水位置奔向第 \(i\) 个露水位置中花费了 \(p\) 点时间,那么对于任意的 \(j\in [i, k]\),第 \(j\) 滴甲虫要喝的露水都会减少 \(p\) 的价值。而我们只是企图找到露水贡献的总和,或者我们考虑其对偶问题就是失掉的贡献总和。
那么根据我们上面的分析,要算这类贡献,我们不必算每个露水失掉的贡献,而是横着来,算某一段贡献对多少个露水有影响,用露水个数乘上这段贡献,再把时间贡献段的贡献相加,依然能得到最终的答案。
在这种分析的基础上,我们就能自然而然的想到枚举你想要获得的露水个数,因为你想要费用前置,必须要知道费用要花到多少个露水的身上。我们非常容易的就会了转移。
技巧 2:
很容易想到,我们的 DP 还有一些问题。由于时间不能为负,而在费用前置的过程中,我们根本没有管这一问题,只是一味的计算某一段时间,并摊给需要减少贡献的水滴。但是其实你注意到,由于我们要求的是减少贡献的最小值,所以在时间为负的情况下,这样一定不优。如何理解?即你还不如减少一个位置,如果你多加了这个位置,虽然你多了 \(m\) 的贡献,但是你减少的贡献比 \(m\) 还大,这显然是不优的。
小技巧:
由于可能存在 \(x_i=0\),所以我们根本不必要插入一个没有贡献的起始点来麻烦自己。可以直接计算每个点单独作为第一个贡献点贡献,这样显然是没有问题的。
优化方向:
获得第 \(i\) 个露水的贡献后,获得第 \(i+1\) 个露水的贡献显然更少,故以获得露水个数为横坐标,答案为纵坐标构成的图像显然是一个上凸壳,而且除了最大值部分,不可能存在平台,于是我们可以考虑三分优化掉枚举露水个数这一步,从而获得一个 \(O(n^2\log n)\) 的复杂度。
P4657 [CEOI 2017] Chase
本题所使用到的技巧:
- 树上特殊两点维护手法:钦定一点为根
- 换根 DP,最大值次大值类换根维护方法
技巧 1:
首先我们考虑设计一个朴素的 DP,因为有树上特殊两点,即一个入口一个出口。那么我们考虑钦定一个特殊点为根,在树上找一个最优位置 \(x\),使得根到 \(x\) 这条路径是最优的。
不难想到我们应当枚举根来做这个事,而其优化方法也就呼之欲出了:换根 DP。不过我们不急着优化它。首先设计状态,考虑一下除了位置和背包这两位还有啥,非常简单,就是你自己选不选。于是我们设计 \(f_{i,j,0/1}\) 代表以钦定根为入口,在 \(i\) 子树内任意钦定一点为出口,选了 \(j\) 个磁铁,\(i\) 位置放/不放磁铁的最优方案。
转移是较为容易的,只要你理解了题意。如果你决定不选,那么你对答案必然贡献为 \(0\),于是对你儿子的两种状态取个最大值即可。如果你决定选,那么你对答案的贡献是 \(\sum_{v\in son_u} a_v\),于是在你儿子的两种状态取最大值之后,再加上这个贡献即可。
这个能有 70 分。
技巧 2:
你想到这个是简单的,写起来是及其痛苦的。
非常套路的考虑换根,你的目的还是除去 \(v\) 这一儿子对 \(u\) 的贡献,那么如果原先对 \(u\) 有贡献的不是 \(v\),那好办,直接用就行,如果是 \(v\),你就需要找到树上次长链进行转移。于是我们只能暴力维护最长链和次长链,算出 \(u\) 除去 \(v\) 的贡献后,再取最大值算出答案。
而且注意此时 \(v\) 变为了根,那么 \(u\) 也是它的儿子,应当被算在贡献里,于是你最好开一个 \(w_u\) 代表 \(u\) 的贡献以便动态维护。
剩下的便是非常非常困难,细节巨多的换根 DP,我还没有完全理解题解在干什么,这里先咕一下这种换根的维护手法,会补。
P6669 [清华集训 2016] 组合数问题
本题所使用到的技巧:
- Lucas 定理表示 \(k\) 进制
- 枚举两数大小相关数位 dp 限制
技巧 1:
注意到 \(k\) 是质数,所以我们可以对组合数施 Lucas 定理。得到 \(\binom{n}{m} = \binom{[\frac{n}{k}]}{[\frac{m}{k}]}\times \binom{n\bmod k}{m\bmod k}\),然后由于 \(k\) 较小,我们可以分解很多次。而由于 Lucas 定理本身具有递归处理性质,我们想到拆位后,单独对拆出来的组合数进行考虑。
这启发我们进行数位 dp。我们先不考虑题目中的 \(j\leq i\) 的限制。那么我们考虑对组合数和 \(k\) 的整除性进行分类讨论。
由 Lucas 定理的正确性,若 \(\binom{i}{j}\) 中 \(i<k\) 且 \(j<k\),那么 \(\binom{i}{j}\) 要么为 \(0\),要么不是 \(k\) 的倍数。那么组合数何时为 \(0\) 呢?非常简单,\(i<j\) 即是其充要条件。
于是,我们将问题转化为,在将 \(\binom{n}{m}\) 用 Lucas 定理拆位后,判断是否存在一个拆出来的组合数 \(\binom{i}{j}\),满足 \(i<j\)。于是我们将问题转化为了数位 dp 问题。
设 \(g_{step,f1,f2,f}\) 表示考虑了前 \(step\) 位,枚举的 \(i\) 现在是否与 \(n\) 满足严格偏序关系,枚举的 \(j\) 现在是否与 \(m\) 满足严格偏序关系,是否满足上面提到的满足有贡献的条件。数位 dp 记忆化搜索即可。
技巧 2:
我们现在来考虑 \(j\leq i\) 的限制。由于我们从高往低位考虑。所以我们对 \(j\) 和 \(i\) 的偏序关系,其实和 \(i\) 与 \(n\) 的偏序关系没有本质区别,于是我们直接沿用那种写法,这里也处理一下就好了。启发在于,数位 dp 只是数位比较的过程,数的大小关系是可以随着数位 dp 进行动态维护的。
于是我们将状态变为,\(g_{step,f1,f2,f3,f}\) 表示考虑了前 \(step\) 位,枚举的 \(i\) 现在是否与 \(n\) 满足严格偏序关系,枚举的 \(j\) 现在是否与 \(m\) 满足严格偏序关系,\(i\) 与 \(j\) 是否满足严格偏序关系,是否满足上面提到的满足有贡献的条件。
CF1063F String Journey
本题所使用到的技巧:
- 枚举答案
- 类线段树优化 dp 的横向按层转移
其实并不是很难的一道题,如果你写的是 \(\sqrt{n}\) 的歪解的话。
技巧 1:
我们来分析答案的上界,设 \(k\) 为其答案。
显然的,我们从长度为 \(1\) 开始,每次就只加一个字符,因为加多个字符一定不优,所以长度为 \(i\) 的串一定是长度为 \(i-1\) 的串在 开头/结尾 拼上一个字符构成的。
于是我们就可以分析了。答案需要的长度是 \(\frac{(k+1)\times k}{2}\),它要小于等于 \(n\),于是 \(k\leq \sqrt{2n}\)。
于是这启发我们枚举答案。
技巧 2:
由于我们枚举答案。我们很容易想到,把枚举答案作为第一重循环,第二层循环进行 check。这非常类似线段树优化 dp,也是把上一层的状态压进线段树,然后处理这一层的答案。那么这里我们可以不用线段树,而是把状态存进 bitset,\(f_{i,j}\) 表示答案为 \(i\) 时,使用存在开头为 \(j\) 的合法串。甚至我们还可以不用压进 bitset,因为我们每一层的状态只与上一层相关,我们可以进行滚动数组优化空间,这里的优化是有必要性的,虽然不优化也可以。
那么转移其实是比较显然的,我们可以注意到,我们唯一需要判断的,就是快速判断一个字符串是否存在,这可以字符串哈希加上 umap 或者 bitset。
对于每个位置 \(j\),我们要预留出 \(i\) 长度的串,然后判断 \(j+i\) 到 \(n\) 是否存在一个位置 \(p\) 使得 \(f_{i-1,p}\) 为 true,且以 \(p\) 开始,长度为 \(i-1\) 的串,是 \(s[i : i+j-1]\) 串的一个最长真前缀或者最长真后缀,这个判两次就行。
然后你只要倒着做,倒着加长度为 \(i-1\) 的串,你就会发现这些限制都是好实现的。
[AGC002F] Leftmost Ball
本题所使用到的技巧:
- 按组转移
- 强制限定保证不重不漏
先说不能组合数学的原因,因为加了一个白球,你后面可选的球就增加了 \(k-1\),而如果你直接组合数学,可能会出现选的球不够的不合法情况,且很难排除。
拿到这道题的时候,第一个应当想到的是 \(f_{i,j}\) 代表前 \(i\) 个位置,选了 \(j\) 个白球的方案数,然后乘以组合系数进行转移,最后再除以一个多重集组合数要除以的东西。这样朴素转移是 \(O(n^3k^2)\) 的,我们可以前缀和优化,注意到转移方程的前缀方案数和确实是可以增量计算的,但是没有什么用,即使你写出来也是 \(O(n^2k)\)。其实你最开始就应该很快把这种 dp pass 掉,因为其状态定义就是 \(n^2k\)。
技巧 1:
我们考虑更高妙的设计状态的方法,考虑一种颜色一种颜色往里填,那么设计状态 \(f_{i,j}\) 表示放了 \(i\) 个白球,放了 \(j\) 个完整的颜色的方案数。这样的优势在于,我们有一些充要条件,就是对于每个前缀,\(i\) 都大于等于 \(j\) 即可,这样直接方便了转移。
技巧 2:
考虑转移,我们强制钦定一些东西保证不重不漏:
- 放白球的时候放在可填的最左边,这是很自然的,因为白球本来就代表颜色的最左边。
- 填序列的时候我们可以选颜色,但是这个颜色的第一个位置必定要填在空着的第一个位置。这也非常好理解,就是说你第一个不在最左边,那你必定要有一个填在最左边,而你颜色又是任选的,必然会导致重复。
两种转移:
- 填白球,即 \(f_{i-1,j}\)。
- 填一个长度为 \(k-1\) 的颜色序列,首先选颜色,即 \(n-j+1\),然后选位置,即 \(\binom{nk−i−(j−1)(k−1)−1}{k-2}\),解释一下,首先把 \(i\) 个白球排除,再把已填的完整颜色排除,再排除此颜色的第一个(因为已经钦定了位置),然后剩下 \(k-2\) 个任选。
然后完了,时间复杂度 \(O(n^2)\),需要与处理阶乘,阶乘逆元以求得组合数。与 [ARC162E] Strange Constraints 有几分相像。

浙公网安备 33010602011771号