dp 笔记
背包类型
01 背包
这种类型的典型问题就是给定一个总数,再给定 n n n 个物品,每个物品都是消耗一定的数值得到一定的数值。
这种问题最开始会想到用贪心解决。
但是有时候贪心为了拿最高的,舍掉了两个次高的,但恰好两个次高的和比最高的更大,这样就亏大了。
动态规划,就是从小的往大的推,我们不妨设 dp[i][j] = 如果有 i 个物品;总数,也就是背包体积为 j,最佳答案是多少。
那么对于每个物品有两种状态,选 和 不选。我们肯定是选两种中最优的方法,那么当可以选的时候,可以用 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 消耗] + 价值)
这样已经很好了,但是空间还是不够优。
我们可以试着让这个数组降一维。我们发现 dp 数组的第一维,都是从
i
−
1
i - 1
i−1 转移过来的,我们可以直接忽略掉第一维,但是需要注意到是因为如果我们的第二层循环是正的,那么 dp[i][j]
就会从 dp[i][]
传过来,就不是从 dp[i - 1][]
传过来,这样的话一个物品就可以拿无限次了,就变成了完全背包。我们的循环要倒着。
为了免掉判断,我们的第二层循环可以直接到这个物品的消耗就停,毕竟后面的肯定是无法拿。
这样的时间复杂度是 O(n(V-w[i]))
空间复杂度是 O(V)
例题+代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
int dp[10005] = {0}, n, V, v, w;
cin >> V >> n;
for (int i = 1; i <= n; i++) {
cin >> w >> v;
for (int j = V; j >= w; j--) {
dp[j] = max(dp[j], dp[j - w] + v);
}
}
cout << dp[V];
return 0;
}
完全背包
完全背包和 01 背包的最大的不同就是一个物品可以拿多次。
在两维 01 背包中的第一个维度就是控制一个物品只能那一次,不管要不要去都是从上一个传承下来的。但是完全背包的这一维就可以直接从自己传承来,也就是可以无限重复拿。
既然这样,我们可以直接把这意味搞掉,而不用对循环进行操作。
大部分背包都是从 01 背包改下来的,我们做背包题目的时候,只要发现背包的几要素,就可以轻而易举的解决了。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
int dp[10005] = {0}, n, V, v, w;
cin >> V >> n;
for (int i = 1; i <= n; i++) {
cin >> w >> v;
for (int j = w; j <= V; j++) {
dp[j] = max(dp[j], dp[j - w] + v);
}
}
cout << dp[V];
return 0;
}
分组背包
讲完完全背包,我们来看一下分组背包。
这种背包其实就是 01 背包加一个分组(废话)。
做这种题目时很容易想到要在 01 背包的循环上面套一个组别的循环,但是顺序可有大讲究。
这种背包最外层时组别的循环,因为这种题目可以划分为多个 01 背包,每个 01 背包互补相关。组别的循环就要在最外层。
第二层是是重量的枚举。这里跟 01 背包不同,因为如果这个放在最下面,会造成冲突。这里也是需要倒序的。
第三层是每个物品的枚举。
这里需要注意的是我们需要像二维 01 背包一样做一个特判,判断是否装得下,因为我们把多个 01 背包合在了一个维度。
下一步就像 01 背包一样了,不多说了~
其他背包就不多讲了,我们其实只要找到背包的:
- 总钱数
- 每个物品(多维也行)
- 时间限制和特殊说明(有可能有)
- 我会不会做这道题(doge)
简单dp
P1481 魔族密码 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
比如这道题,我们按题意定义 dp[i]
为第
i
i
i 个字符串的答案是多少。
还是按背包的思路,我们有两种选择,就是选不选这个字符串。
如果不选,那么直接不动。
如果选的话,遍历第
1
1
1 个字符串到第
i
−
1
i - 1
i−1 个字符串,看看这个字符串是接着的哪个字符串。找到后就从那个字符串 + 1
。
对于这道题,我们采取的思路是选或不选,这个思路也可以推广到很多 dp 的题,包括 2022 年,csp-j 的第四题。
还有一个窍门就是我们可以直接按题目的问题去 ”抄“状态。
比如这道题,问题是
现在你要做的就是在一个给定的单词表中取出一些词,组成最长的词链,就是包含单词数最多的词链。将它的单词数统计出来
那么我们就设 dp[i]
为第
i
i
i 个字符串的答案是多少。
又比如说P1359 租用游艇 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
问题是
计算出从游艇出租站 1 到游艇出租站 n 所需的最少租金。
那么直接设 dp[i] = 从第一站到第 i 站所需的最少租金
#include <bits/stdc++.h>
using namespace std;
/*
*/
int a[1005][1005], dp[100005], n;
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i ++) {
for (int j = i + 1; j <= n; j ++) {
cin >> a[i][j];
}
}
for (int i = 2; i <= n; i ++) {
for (int j = 1; j <= i; j ++) {
dp[i] = (!dp[j] ? dp[j] + a[j][i] : min(dp[i], dp[j] + a[j][i]));
}
}
cout << dp[n];
return 0;
}
一定要记住