近期部分 dp 题总结(rating<=2400)
主体是 CF/洛谷 的题,当然 AT 以及别的题目也会有。
篇幅问题,本文不贴代码只给代码链接(部分在 github 上),如果看不了代码的可以对应网站搜 Plozia 看代码。
注意本文部分 CF 链接代码是镜像站。
个人总结。
CF148E Porcelain
首先预处理出 \(f_{i,j}\) 表示第 \(i\) 行选 \(j\) 个物品的最大价值,然后设 \(g_{i,j}\) 表示前 \(i\) 行,总共选 \(j\) 个物品最大价值,枚举第 \(i\) 行物品数转移即可,其实就是个分组背包。Code
CF41D Pawn
下令 \(p=k+1\),然后 \(k\) 会被拿来做 dp 的维度,别弄错。
注意到这道题要膜 \(p\),因此正常两维 dp 做不了,考虑设 \(f_{i,j,k}\) 表示当前走到第 \(i\) 行第 \(j\) 列,目前总豌豆个数膜 \(p\) 之后为 \(k\) 时每个士兵能分到的豌豆数。
枚举 \(i,j\) 以及上一格的余数 \(k\),则有如下转移方程:
对于 \(j=1\) 或 \(n-1\) 的情况,显然上面两个式子中有一个无意义,另外一个有意义,直接转移即可。
最后答案 \(\max\{f_{1,j,0}\mid j\in[1,m]\}\times p\),初值 \(f_{n,j,a_{i,j}\bmod p}=\Big\lfloor\dfrac{a_{i,j}}{p}\Big\rfloor\),其余全为极小值。
转移的时候顺便开一个 \(g\) 数组表示当前状态是从哪里转移来,输出方案的时候根据 \(g\) 数组输出即可。Code
[USACO 2004 OPEN Gold]Turning in Homework
首先注意到对于还没交作业的区间 \([l,r]\),一定是交端点时顺便把中间交了更优,因为你交端点同时可以顺便把中间交了,但是如果先交中间则还要去端点交。
据此先将输入按 \(x\) 升序排序,设 \(f_{i,j,0/1}\) 表示 \([i,j]\) 没交作业,剩下的都交了,当前正在交 \(i/j\) 的最小时间(注意交好了),初始化 INF,\(f_{1,C,0}=\max\{a_1.x,a_1.t\},f_{1,C,1}=\max\{a_C.x,a_C.t\}\)。
则有如下转移方程:
注意转移的时候不要算上初始状态 \(f_{1,C,0},f_{1,C,1}\)。
最后答案就是 \(\min_{i \in [1,n]}\{\min\{f_{i,i,0},f_{i,i,1}\}+|b-a_i.x|\}\),\(b\) 是门的位置。Code
[USACO 2008 NOV Gold]Mixed Up Cows
注意到 \(n \leq 16\),显然状压,设 \(f_{S,x}\) 为当前已经选入数列的数状态为 S,末尾数字为 \(a_x\) 时的方案数,这里只记录末尾数字是因为方案是否合法只与末尾数字与接上来的数字有关。
另枚举 \(j\) 表示接在 \(i\) 前面的数字,需要保证 \(i,j\) 都在 \(s\) 当中,转移就是 f[s][i] += f[s ^ (1 << (i - 1))][j],就是从以 \(j\) 为结尾数列中接上一个 \(i\)。
初始值为对于任意 \(i \in [1,n]\),f[1 << (i - 1)][i] = 1,最后答案为所有 f[(1 << n) - 1][i] 之和,记得 long long。Code
CF9D How many trees?
正着做不行就反着做,设 \(f_{n,h}\) 表示节点个数为 \(n\),高度小于等于 \(h\) 的方案数,注意两维上界都是 \(n\),初值 \(f_{0,i}=1\) 其余均为 0,转移考虑枚举左右子树节点个数,\(f_{j,i}=\sum_{k=0}^{j-1}f_{k,i-1}\times f_{j-k-1,i-1}\),最后答案 \(f_{n,n}-f_{n,h-1}\),记得开 long long。Code
CF1067A Array Without Local Maximums
设 \(f_{i,v,0/1/2}\) 表示前 \(i\) 个,\(a_i=v\),\(a_{i-1}\) 与 \(a_i\) 大小关系为小于/大于/等于的方案数,dp 式子比较简单,拿前缀和优化即可。Code
CF337D Book of Evil
考虑换根 dp,设 \(f_{i,0/1}\) 表示 \(i\) 点到子树内标记点的最大值和次大值,但是这俩不能出自同个子树,一遍 dp 之后设 \(g_i\) 表示 \(i\) 点到其子树外的标记点距离最大值,设 \(u \to v\),如果 \(u\) 点最大值由 \(v\) 转移则 \(g_v=\max\{g_u+1,f_{u,1}+1\}\) 否则 \(g_v=\max\{g_u+1,f_{u,0}+1\}\),最后答案就是 \(f_{i,0}\le d,g_i\le d\) 的点个数。Code
CF149D Coloring Brackets
设状态 \(f_{l,r,0/1/2/,0/1/2}\) 为区间 \([l,r]\),左端点无颜色/红色/蓝色,右端点无颜色/红色/蓝色的方案数,设 Match[l] 表示与 \(l\) 匹配的括号位置,考虑如下三种情况:
- \(r==l+1\),此时直接 \(f_{l,r,0,1}=f_{l,r,0,2}=f_{l,r,1,0}=f_{l,r,1,2}=1\)。
- \(r\ne l+1,Match_l=r\),此时枚举 \([l+1,r-1]\) 左右端点颜色,对 \(f_{l,r,(0,1)/(0,2)/(1,0)/(2,0)}\) 暴力更新答案。
- \(Match_l\ne r\),此时枚举 \([l,Match_l],[March_l+1,r]\) 左右端点颜色暴力更新所有 \(f_{l,r,*,*}\),特别注意 \(Match_l,Match_l+1\) 颜色不能相同。
具体看代码,写代码的时候考虑采用记忆化搜索这样所有区间一定是合法的(即所有枚举到的区间一定是一个括号序列)。Code
CF1152D Neko and Aki's Prank
考虑构造一种方案,深度为偶数的点往父亲连边,一个点连了多条边就删去多余边,然后就有奇数深度点都连了一条边,可以证明这就是最大答案,于是问题变成了求奇数深度点个数。
设 \(f_{i,j},j\le i\) 表示从根节点到这个点,有 \(i\) 个左括号,\(j\) 个右括号,当前节点可能的个数,特别的 \(j>i\) 认为 \(f_{i,j}=0\),有转移方程 \(f_{i,j}=f_{i-1,j}+f_{i,j-1}\),最后答案就是 \(i+j\) 为奇数的 \(f_{i,j}\) 之和。Code
CF1557D Ezzat and Grid
考虑做反面,设 \(f_i\) 表示 \(1\sim i\) 中最多能保留几行,首先将每行对应线段存下,显然方程有 \(f_i=\max f_j+1\),其中 \(i,j\) 能相连,也就是区间有覆盖,这个可以在 \([1,10^9]\) 上写个动态开点线段树,每个点权值就是所有覆盖这个点的行数中 \(f\) 的最大值,每次区间上询问最大值转移,然后对所有的覆盖区间进行区间赋值操作,注意由于询问时取的最大值因此可以证明所有被覆盖的点答案一定都会变大,所以区间赋值即可,方案就记录一下转移方向,线段树数组开大。Code
P5322 [BJOI2019] 排兵布阵
设 \(f_{i,j}\) 为考虑前 \(i\) 个城堡,总共派出 \(j\) 个士兵的最大分数,考虑对于对手 \(i\) 和城堡 \(j\),一定是只派出 \(2\times a_{i,j}+1\) 最优,因此考虑将对于每个对手夺取城堡 \(i\) 所需最小士兵值存到 \(p_{i,?}=2\times a_{i,?}+1\) 里面,即 \(p_{i,k}\) 表示从 \(k\) 个对手中夺取城堡 \(i\) 所需要的最小兵力,对每个 \(p_i\) 升序排序后滚动数组有转移方程 \(f_j=\max\{f_{j-p_{i,k}}+k\times i\}\),复杂度 \(O(nms)\) 但是确实能过。Code
P2051 [AHOI2009] 中国象棋
设 \(f_{i,j,k}\) 表示前 \(i\) 行,有 \(j\) 列放了一个炮,有 \(k\) 列放了两个炮,考虑当前这一行放零个一个两个炮,放在哪几个列的对应位置转移即可。Code

浙公网安备 33010602011771号