DP 状态设计

这是一篇总结了小部分 DP 状态设计的总结。

1. 可以交换值域和dp状态的某个维度

简单来说,就是把自己要求的放到其中的一维,把自己存不下的一维变成 dp 记录的东西,最后靠枚举来取得答案。

适用范围:要求答案很小但其中一维范围很大。

我们先来举个例子。

例题 atcoder dp_e

对于这道题,第一眼肯定是用 01背包 去做,但我们观察到 \(1 \le W \le 10^9\),所以按照正常的背包肯定做不了。

但是我们有注意到他的价值非常的小仅有 \(10^5\) 所以我们定义状态为:\(dp_{i,j}\) 表示枚举到第 \(i\) 个物品时,总价值为 \(j\) 的方案的最小重量。我们可以像 01背包 一样压缩成一维,也就是 \(dp_i\) 表示总价值为 \(j\) 的方案的最小重量。

最后从大往小枚举符合条件的价值就行了。

戳我看代码喵~
int n, m;
int dp[N];
int w[N], v[N];

signed main() {
	n = re, m = re;
	int M = 0;
	for (int i = 1; i <= n; i++) w[i] = re, v[i] = re, M += v[i];
	memset(dp, 0x3f, sizeof dp);
	dp[0] = 0;
	for (int i = 1; i <= n; i++) for (int j = M; j >= v[i]; j--) dp[j] = min(dp[j], dp[j - v[i]] + w[i]);
	for (int i = M; i; i--) if (dp[i] <= m) wr(i), endl, exit(0);
}

2. 位置型 dp

一般状态表示

\(dp_i\) 表示前 \(i\) 个 / 第 \(i\) 个...

\(dp_{i, j}\) 表示第 \(i\)\(j\) 列...

\(dp_{l, r}\) 表示区间 \([l, r]\) 内...

\(dp_u\) 表示以子树 \(u\) 为根...

总结一下

设计一个好的 dp 有以下几步:

  1. 先写一个一定正确的暴力状态
  2. 指出它的后效性或复杂度瓶颈
  3. 找哪些历史对未来等价,删掉无关维度
  4. 证明同状态历史对所有未来转移完全相同
  5. 最后再考虑如何优化转移
posted @ 2026-08-14 21:24  OiLight  阅读(4)  评论(0)    收藏  举报