3.30
四、背包 DP 模板
背包最核心区别
• 01 背包:每个物品最多选一次,容量 j 倒序。
• 完全背包:每个物品可以选无限次,容量 j 正序。
• 方案数问题:通常 dp[0] = 1,转移用加法。
- 01 背包
题型特征:每个物品只能选 0 次或 1 次,有容量限制,求最大价值。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, V;
cin >> n >> V;
vector<int> dp(V + 1, 0);
for (int i = 1; i <= n; i++) {
int w, val;
cin >> w >> val;
for (int j = V; j >= w; j--) {
dp[j] = max(dp[j], dp[j - w] + val);
}
}
cout << dp[V] << '\n';
return 0;
}
- 完全背包
题型特征:每个物品可以选无限次,有容量限制,求最大价值。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, V;
cin >> n >> V;
vector<int> dp(V + 1, 0);
for (int i = 1; i <= n; i++) {
int w, val;
cin >> w >> val;
for (int j = w; j <= V; j++) {
dp[j] = max(dp[j], dp[j - w] + val);
}
}
cout << dp[V] << '\n';
return 0;
}
- 多重背包:二进制优化
题型特征:每种物品最多有 cnt 个。常用二进制拆分,把多重背包转成 01 背包。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, V;
cin >> n >> V;
vector<int> dp(V + 1, 0);
for (int i = 1; i <= n; i++) {
int w, val, cnt;
cin >> w >> val >> cnt;
int k = 1;
while (cnt > 0) {
int num = min(k, cnt);
int weight = num * w;
int value = num * val;
for (int j = V; j >= weight; j--) {
dp[j] = max(dp[j], dp[j - weight] + value);
}
cnt -= num;
k *= 2;
}
}
cout << dp[V] << '\n';
return 0;
}
- 分组背包
题型特征:物品被分成若干组,每组最多选一个。
#include <bits/stdc++.h>
using namespace std;
int main() {
int g, V;
cin >> g >> V;
vector<int> dp(V + 1, 0);
for (int i = 1; i <= g; i++) {
int cnt;
cin >> cnt;
vector<int> w(cnt + 1), val(cnt + 1);
for (int k = 1; k <= cnt; k++) cin >> w[k] >> val[k];
for (int j = V; j >= 0; j--) {
for (int k = 1; k <= cnt; k++) {
if (j >= w[k]) {
dp[j] = max(dp[j], dp[j - w[k]] + val[k]);
}
}
}
}
cout << dp[V] << '\n';
return 0;
}
- 恰好装满背包
题型特征:要求容量必须刚好等于 V,不是“不超过 V”。初始化和普通背包不同。
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;
int main() {
int n, V;
cin >> n >> V;
vector<int> dp(V + 1, -INF);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
int w, val;
cin >> w >> val;
for (int j = V; j >= w; j--) {
dp[j] = max(dp[j], dp[j - w] + val);
}
}
if (dp[V] < 0) cout << "impossible\n";
else cout << dp[V] << '\n';
return 0;
}
- 背包求方案数
题型特征:问有多少种方法凑出容量 V,不是最大价值。
// 01 背包方案数:每个数只能用一次
vector<long long> dp(V + 1, 0);
dp[0] = 1;
for (int i = 1; i <= n; i++) {
int w;
cin >> w;
for (int j = V; j >= w; j--) {
dp[j] += dp[j - w];
}
}
// 完全背包方案数:每个数可以无限次使用
vector<long long> dp(V + 1, 0);
dp[0] = 1;
for (int i = 1; i <= n; i++) {
int w;
cin >> w;
for (int j = w; j <= V; j++) {
dp[j] += dp[j - w];
}
}
- 二维费用背包
题型特征:有两个限制条件,例如体积和重量、时间和金钱。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, V, M;
cin >> n >> V >> M;
vector<vector<int>> dp(V + 1, vector<int>(M + 1, 0));
for (int i = 1; i <= n; i++) {
int v, m, val;
cin >> v >> m >> val;
for (int j = V; j >= v; j--) {
for (int k = M; k >= m; k--) {
dp[j][k] = max(dp[j][k], dp[j - v][k - m] + val);
}
}
}
cout << dp[V][M] << '\n';
return 0;
}
五、路径 DP
- 网格最小路径和
题型特征:从左上角走到右下角,每次只能向右或向下,求最小路径和。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> a(n + 1, vector<int>(m + 1));
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 1e9));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) cin >> a[i][j];
}
dp[1][1] = a[1][1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (i == 1 && j == 1) continue;
if (i > 1) dp[i][j] = min(dp[i][j], dp[i - 1][j] + a[i][j]);
if (j > 1) dp[i][j] = min(dp[i][j], dp[i][j - 1] + a[i][j]);
}
}
cout << dp[n][m] << '\n';
return 0;
}
- 网格路径方案数
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<long long>> dp(n + 1, vector<long long>(m + 1, 0));
dp[1][1] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (i == 1 && j == 1) continue;
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
cout << dp[n][m] << '\n';
return 0;
}
六、股票 DP
1. 只能买卖一次
int minPrice = price[0];
int ans = 0;
for (int i = 1; i < n; i++) {
ans = max(ans, price[i] - minPrice);
minPrice = min(minPrice, price[i]);
}
2. 可以买卖无限次
int ans = 0;
for (int i = 1; i < n; i++) {
if (price[i] > price[i - 1]) {
ans += price[i] - price[i - 1];
}
}
3. 最多买卖 K 次
vector<int> buy(K + 1, -1e9);
vector<int> sell(K + 1, 0);
for (int p : price) {
for (int j = 1; j <= K; j++) {
buy[j] = max(buy[j], sell[j - 1] - p);
sell[j] = max(sell[j], buy[j] + p);
}
}
cout << sell[K] << '\n';
七、比赛易错点清单
易错点 正确做法
01 背包循环方向写反 容量 j 从 V 到 w 倒序遍历
完全背包循环方向写反 容量 j 从 w 到 V 正序遍历
恰好装满初始化错误 dp[0]=0,其余设为 -INF 或 INF
方案数忘记 dp[0]=1 什么都不选凑出 0 是 1 种方案
答案太大溢出 优先用 long long,题目要求时取模
最大值初始值过小/过大错误 最大值用 -INF,最小值用 INF
数组下标混乱 字符串一般 s[i-1] 对应 dp 第 i 位
n=1 边界漏掉 打家劫舍、爬楼梯、股票题要特判
八、赛前最推荐背熟的 8 个模板
- 01 背包
- 完全背包
- 多重背包二进制优化
- LIS:O(n²) 和 O(n log n)
- LCS
- 最大子段和
- 打家劫舍
- 网格路径 DP
九、万能 C++ 开头模板
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int INF = 1e9;
const ll LINF = 4e18;
const int MOD = 1e9 + 7;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
return 0;
1.
怎么将浮点数转换为字符串
输入:直接把sring类型改为double 输入就好了
或加‘0’
浙公网安备 33010602011771号