背包问题
发表于更新于
字数总计:1.5k阅读时长:6分钟阅读量: 济宁
数据结构算法背包问题
wypAcWing 2. 01背包问题
1 2 3 4 5 6
| 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 输出最大价值。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int v[N], w[N], f[N]; int n, m;
int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i ++ ) scanf("%d%d", &v[i], &w[i]); for (int i = 1; i <= n; i ++ ) for (int j = m; j >= v[i]; j -- ) f[j] = max(f[j], f[j - v[i]] + w[i]); cout << f[m] << endl; return 0; }
|
对f[i]
有影响的只有f[i-1]
,所以为了避免用更新过的f[i-1]
来更新f[i]
,我们的代码是从大到小来更新
AcWing 3. 完全背包问题
跟0-1背包问题的区别是:01背包问题,一个物品只能选择一次,为了用更新过的f[i-1]
来更新f[i]
,0-1背包问题从大到小更新。
而完全背包问题可以选择无限次,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m; int v[N], w[N], f[N];
int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i ++ ) scanf("%d%d", &v[i], &w[i]); for (int i = 1; i <= n; i ++ ) for (int j = v[i]; j <= m; j ++ ) f[j] = max(f[j], f[j - v[i]] + w[i]); printf("%d", f[m]); return 0; }
|
第 i
件物品 可以选多次,所以要从 小到大遍历 j,保证 f[j - v[i]]
已经计算的是本轮更新后的 f[j - v[i]]
,即包含了重复选物品的可能。
AcWing 4. 多重背包问题(一)
多重背包问题也是0-1背包问题的一个变式。与 0-1 背包的区别在于每种物品有 ki个,而非一个。
注意:完全背包问题是每种物品无限个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m; int v[N], w[N], s[N]; int f[N][N];
int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i ++ ) scanf("%d%d%d", &v[i], &w[i], &s[i]); for (int i = 1; i <= n; i ++ ) for (int j = 0; j <= m; j ++ ) for (int k = 0; k <= s[i] && k * v[i] <= j; k ++ ) f[i][j] = max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k); printf("%d", f[n][m]); return 0; }
|
每次都枚举当前体积为j
的所有可能装法;有条件限制,不会想完全背包问题一样出现下标为负的问题
AcWing 5. 多重背包问题(二)
这里介绍的是多重背包问题的二进制优化,将一个最多选 s
次的物品,拆成若干个 01背包问题中的物品。
核心思想是:
把一个数 s
拆成若干个 2 的幂的和:如 s = 13
拆成 1 + 2 + 4 + 6
(其中最后一项是余数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include <bits/stdc++.h>
using namespace std;
const int N = 12010, M = 2010;
int n, m; int v[N], w[N]; int f[M];
int main() { scanf("%d%d", &n, &m); int cnt = 0; for (int i = 1; i <= n; i ++ ) { int a, b, s; scanf("%d%d%d", &a, &b, &s); int k = 1; while (k <= s) { cnt ++ ; v[cnt] = a * k; w[cnt] = b * k; s -= k; k *= 2; } if (s > 0) { cnt ++ ; v[cnt] = a * s; w[cnt] = b * s; } } n = cnt ; for (int i = 1; i <= n; i ++ ) for (int j = m; j >= v[i]; j -- ) f[j] = max(f[j], f[j - v[i]] + w[i]); printf("%d", f[m]); return 0; }
|
🔢 举个例子:s = 13
拆分轮次 |
k值 |
s剩余 |
体积 v[cnt] |
价值 w[cnt] |
初始 |
1 |
13 |
a×1 |
b×1 |
第1轮 |
2 |
12 |
a×2 |
b×2 |
第2轮 |
4 |
10 |
a×4 |
b×4 |
第3轮 |
8 |
6 |
❌ 不满足 k <= s ,跳出 while |
|
还剩下 s = 6
,需要补一个:
1 2 3 4 5 6
| if (s > 0) { cnt ++; v[cnt] = a * s; w[cnt] = b * s; }
|
最终拆成了:1件、2件、4件、6件(总共13件 ✅) 每件价值 体积都不一样, 化简为01背包问题了
AcWing 9. 分组背包问题
题目描述:有n
组物品和一个大小为m
的背包,第i
个物品的价值为wi
,体积为vi
。同时,每个物品属于一个组,同组内最多选择一个物品(可以不选)。求背包能装载物品的最大总价值
思考:其实就是从「在所有物品中选择一件」变成了「从当前组中选择一件」,于是就对每一组进行一次 0-1 背包就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m; int v[N][N], w[N][N], s[N]; int f[N];
int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i ++ ) { scanf("%d", &s[i]); for (int j = 0; j < s[i]; j ++ ) scanf("%d%d", &v[i][j], &w[i][j]); } for (int i = 1; i <= n; i ++ ) for (int j = m; j >= 0; j -- ) for (int k = 0; k < s[i]; k ++ ) if (v[i][k] <= j) f[j] = max(f[j], f[j - v[i][k]] + w[i][k]); printf("%d", f[m]); return 0; }
|