动态规划刷题集
动态规划的本质就是对状态的提取与压缩。
做 dp 的题务必要考虑下面需要的信息:
- 具体的状态(如果空间不够考虑哪些信息是可以压缩的)。
- 具体的转移(谁转移谁)。
- 初始情况。
- 最终情况。
- 转移是否需要优化。
P1725 琪露诺
\(i\) 可以跳到 \([i+L,i+R]\) 的其中之一,那么 \([i-R,i-L]\) 的任意一格都可以可以跳到 \(i\)。
记 \(f_i\) 为跳到 \(i\) 格获得的最大分数,有如下状态转移方程:
注意答案为 \(\max^{N+R}_{i=N+1} f_i\)。
朴素复杂度为 \(O(N^2)\),上个单调队列即可达到 \(O(N)\)。
P1439 【模板】最长公共子序列
\(O(n^2)\) 做法
记 \(f_{i,j}\) 为 \(P_1\) 枚举的前 \(i\) 项和 \(P_2\) 的前 \(j\) 项的最长公共子序列。
- 当 \(P_1[i]=P_2[j]\) 时,\(f_{i,j}=f_{i-1,j-1}+1\)。
- 否则 \(f_{i,j}=\max(f_{i-1,j},f_{i,j-1})\)。
容易发现第一维可以滚动数组优化掉。这样新状态 \(f_j\) 就表示 \(P_2\) 的前 \(j\) 项与 \(P_1\) 的最长公共子序列。
期望得分 50。
\(O(n \log n)\) 做法
\(P_1\) 与 \(P_2\) 都是 \(1\sim n\) 的一种全排列。
我们可以把 \(P_1\) 全部映射成一个有序的数列,以样例为例:
\(P_1\)3 2 1 4 5可以映射成1 2 3 4 5,那么 \(P_2\) 1 2 3 4 5就变成3 2 1 4 5。
显然新的序列的最长公共子序列与原序列相同。
有一个性质:新的序列 \(P_1\) 的任意一个子序列必然是递增的,因为 \(P_1\) 本身就是递增的,那么对于 \(P_1\) 和 \(P_2\) 的公共子序列也一定是递增的,因此当 \(P_2\) 的某一个子序列递增,那么也一定是 \(P_1\) 的子序列。
只需要求出新的 \(P_2\) 的最长上升子序列即可。
期望得分 100。
P1541 [NOIP2010 提高组] 乌龟棋
记 \(f_{i_1,i_2,i_3,i_4}\) 为使用 \(i_1\) 张 \(1\) 号卡片,使用 \(i_2\) 张 \(2\) 号卡片,使用 \(i_3\) 张 \(3\) 号卡片,使用 \(i_4\) 张 \(4\) 号卡片获得的最大分数。
有如下状态转移方程:
卡片全用的状态即为正确答案。
P10381 「HOI R1」杂赛选比
记 \(f_i\) 为从 \(i\) 开始到 \(n\) 的最大得分。
显然有两种:
- \(i+1\sim i+a[i]\) 的部分全部选上,不解锁。
- 选择该部分其中一个点作为一个解锁点,二者之间的部分加上分数。

状态转移方程:
这样枚举显然是 \(O(n^2)\) 的,但是发现只需要求 \(i+1\sim i+a[i]\) 中的最大值,并且因为在不断转换,需要修改,所以用线段树优化即可。
时间复杂度 \(O(n\log n)\)。
P1435 [IOI2000] 回文字串
令 \(f_{i,j}\) 为区间 \([i,j]\) 的子串变成回文串所需要添加的数量。
若 \(str[i]=str[j]\),则 \(f_{i,j}=f_{i+1,j-1}\)。
否则 \(f_{i,j}= \min (f_{i+1,j},f_{i,j-1})+1\)。
区间 dp 即可,时间复杂度 \(O(n^2)\)。
P8744 [蓝桥杯 2021 省 A] 左孩子右兄弟
如果一个点有 \(x\) 个儿子,显然这个点最多能够提供 \(x\) 的高度。
令 \(f_u\) 为以 \(u\) 为根的子树高度大小,则有如下状态转移方程:
\(f_1\) 为正确答案。
B4016 树的直径
令 \(f_u\) 为以 \(u\) 为根的子树中,从 \(u\) 节点出发到达最长的路径。
则有状态转移方程:
答案为:
P3174 [HAOI2009] 毛毛虫
令 \(f_u\) 为在 \(u\) 节点为根的子树中,以 \(u\) 节点为毛毛虫链的一个端点,所得到最大毛毛虫。
状态转移:
答案为:
注意统计答案的 \(v\) 不能是状态转移选中的点。
不是根节点要加一的原因是它的父节点也包含在内。
P3800 Power收集
很类似于 P1725,\((x,y)\) 可以走到 \((x+1,[y-T,y+T])\),那么 \((x-1,[y-T,y+T])\) 就可以走到 \((x,y)\)。
令 \(f_{i,j}\) 为走到 \((i,j)\) 获得的最大分数。
状态转移:
时间复杂度 \(O(nm^2)\) 显然是不行的,可以单调队列优化至 \(O(nm)\)。
P1156 垃圾陷阱
01 背包变形。
令 \(f_{i,j}\) 表示前 \(i\) 个垃圾中,当前生命为 \(j\) 可以达到的最大高度。
一共有两种处理垃圾的方式:
- 选择放,\(f_{i,j}=f_{i-1,j}+H_i\)。
- 选择吃,\(f_{i,j}=f_{i-1,j-H_{i}}\)。
容易发现这样空间会爆且第一维可以滚动掉,然后就是一个 01 背包了。
P1006 [NOIP 2008 提高组] 传纸条
假设都从 \((1,1)\) 出发。
容易想到设状态为 \(f_{x_1,y_1,x_2,y_2}\),表示当前走到了哪里。本题虽然不卡但是说另一种做法。
因为只能向右或者下走,因此如果走了 \(i\) 步,横坐标都相等时,那么这时候一定相遇。换句话说,如果已经知道当前走了 \(i\) 步且横坐标为 \(x\),那么 \(y\) 坐标也能推出来。
所以新状态 \(f_{L,x_1,x_2}\),表示当前走了 \(L\) 步,第一个纸条横坐标为 \(x_1\),第二的纸条为 \(x_2\) 的最大价值。
那么状态转移就很简单了。
而且转移的时候发现第一维的 \(L\) 只会从 \(L-1\) 转移过去,所以第一维也可以滚掉。
P13020 [GESP202506 八级] 遍历计数
先考虑 dp,假设现在以 \(1\) 号点为根,设 \(f_u\) 是以 \(u\) 为根的子树的答案。根据乘法原理和组合数知识,有转移:
其中 \(son(u)\) 表示 \(u\) 节点的儿子的数量。
那么答案就是以每个点为整棵树的根的答案和,考虑换根 dp。
一个问题:模数是合数,如果换根 dp 显然需要除以贡献。因此如果把状态转移一层一层拆开,有:
\(d(u)\) 表示度数。
这样只需要记录前缀积和后缀积即可,时间复杂度 \(O(n)\)。
P3565 [POI 2014] HOT-Hotels
这题好像可以长链剖分优化 dp,但我不会。
这三个点显然是深度较深的两个的 LCA 到这三个点的距离相同(如果根确定),那么就可以把这个 LCA 拿到根上面,就是三个相同深度的点,这样只需要枚举每个点作为根 dp 就行了。
注意这三个点的两两 LCA 必须都是当前的根节点,那么需要再枚举这个根节点的所有子树。
设 \(b(d)\) 表示当前枚举的子树中深度为 \(d\) 的点的数量,\(f(d)\) 表示前面已经枚举过的子树中选择两个合法点的方案数,\(g(d)\) 表示前面已经枚举过的子树中选择一个合法点的方案数(就是深度为 \(d\) 的点的数量)。
那么每次枚举完:
时间复杂度 \(O(n^2)\)。
CF1606E
设 \(f_{i,j}\) 表示选了 \(i\) 个人,其中生命值最大为 \(j\) 的方案数。
\(i=1\) 时 \(f_{1,j}\) 都等于 \(0\)。
若 \(j \le i-1\),则 \(f_{i,j} = j^i - (j-1)^i\)。
否则,\(f_{i,j} = \sum_{k=2}^n \binom{n}{k} f_{k,j-i+1} (i-1)^{i-k}\),相当于枚举经过一轮后还剩 \(k\) 个人存活。包括哪些人存活、存活的人的方案数、不存活的人的方案数。
CF1716D
可以从 \(k\) 开始依次枚举 \(k\)、\(k+1\)、\(k+2\)。因为要求必须走,所以这一过程不会枚举超过 \(\sqrt n\) 次。
直接设 \(f_i\) 表示第 \(i\) 个位置的情况,\(f_i = f_{i-j}+f_{i-2j} + \dots\)。
但是直接这样转移是有问题的。因为有些情况会中途没有走。
可以令 \(ans_i\) 表示答案。每次 \(j = k\) 变成 \(j=k+1\) 时,从后往前枚举,把上一次的情况覆盖掉,再 \(ans_i \leftarrow f_i\)。这样就能保证每次转移的步是连续的。
这一过程可以前缀和优化。
P3574 [POI 2014] FAR-FarmCraft
设 \(f_i\) 表示 \(i\) 子树内的答案(假设 \(i\) 节点一开始就送到),\(g_i\) 表示在 \(i\) 子树内走一圈回到 \(i\) 的时间(相当于 \(2 size_i -2\))。
令 \(g'(x)\) 表示在转移时 \(g(x)\) 的值,相当于遍历 \(y\) 之前的子树需要的时间。
那么有 \(f(x) = \max (f(x) , f(y) + g'(x)+1)\)。
这是跟 \(y\) 遍历的顺序有关系的。
假设存在另一个在 \(y\) 之后的子树 \(z\),那么 \(f(x) = \max ( f(x) ,\max(f(y),f(z)+g(y)+2)+1+g'(x))\)。
如果交换这两个子树的位置,那么 \(f(x) = \max ( f(x) ,\max(f(z),f(y)+g(z)+2)+1+g'(x))\)。
如果交换更优,那么 \(\max(f(y),f(z)+g(y)+2) > \max(f(z),f(y)+g(z)+2)\)。
因为 \(f(y) < f(y) + g(z) + 2\) 且 \(f(z) < f(z) + g(y) + 2\)。
所以 \(f(y) + g(z) + 2 > f(z) +g(z) + 2\),即 \(f(y)-g(y)>f(z)-g(z)\)。
因此按照 \(f(y)-g(y)\) 由大到小的顺序访问即可。
[NOIP2021] 数列
设 \(f(i,j,k,p)\) 表示考虑在二进制从低到高第 \(i\) 位,已经确定了 \(j\) 个位置,目前有 \(k\) 位是 \(1\),其中第 \(i\) 位需要进 \(p\) 个 \(1\) 的数值和。
状态转移:\(f(i,j,k,p) v_i^t \times \binom{n-j}{t} \rightarrow f(i+1,j+t,k+(t+p)\bmod2,\lfloor \frac{t+p}{2} \rfloor)\)。
答案 \(\sum f(m+1,n,k,p) \times [k + cnt(p) \le K]\),其中 \(cnt(p)\) 表示二进制下 \(p\) 中 \(1\) 的数量。
P2606 [ZJOI2010] 排列计数
原问题等价于 \(n\) 个点的二叉树满足小根堆性质的数量。
对于一棵子树,其根节点一定选最小的那个,发现左子树和右子树互不影响。设 \(f(x)\) 表示有 \(x\) 点的子树的答案,那么这棵子树的答案就是 \(\binom{size(u)-1}{size(lc)}f(size(lc)) f(size(rc))\)。
CF2133D
显然如果不考虑断点,删除 \([l,r]\) 的怪的代价是 \(sum_r -sum_{l-1} - (r-l)\),因为从第 \(l+1\) 个怪开始每一个都会受到一次坠落伤害。
显然需要让坠落伤害尽可能大,因此断点从右到左一定不劣。考虑倒序 dp,设 \(f_i\) 表示删除 \([i,n]\) 的方案数,\(f_{n+1}=0\)。
状态转移:
变形一下可得:
前缀和优化即可。

浙公网安备 33010602011771号