背包问题
01 背包
样例:
4 10
2 1
3 3
4 5
7 9
12// 深搜方式
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int vi[N], wi[N], n, v, ans;
// u 代表当前考虑的物品,物品编号从0开始
// vv 代表已选物品的体积之和
// ww 代表已选物品体积之和是vv时的最大价值
void dfs(int u, int vv, int ww) {
if (u >= n) {
if (ww > ans) ans = ww;
return;
}
dfs(u + 1, vv, ww); // 不选第 u 件物品
if (vv + vi[u] <= v) { // 如果 当前的总体积加上第 u 件物品的体积小于规定体积
dfs(u + 1, vv + vi[u], ww + wi[u]); // 选第 u 件物品
}
}
int main() {
cin >> n >> v;
for (int i = 0; i < n; i++) {
cin >> vi[i] >> wi[i];
}
dfs(0, 0, 0);
cout << ans << endl;
return 0;
}动态规划:
- 阶段划分:按已处理物品的件数将选择过程划分为
个阶段,设阶段变量 代表前 件物品 - 设计状态:
- 设
表示将前 件物品选择若干件放入体积为 的背包获得的最大价值(体积 可以不装满)。 - 设
表示将前 件物品选择若干件恰好放入体积为 的背包获得的最大价值(体积 必须装满)。
- 设
- 决策:考虑第
件物品选还是不选。 - 状态转移方程:
- 边界:
- 对应第一种状态表示:
。 - 对应第二种状态表示:
,其余的
- 对应第一种状态表示:
- 目标解:
- 对应第一种状态表示:
- 对应第二种状态表示:
- 对应第一种状态表示:
下面的参考代码使用的是第一种状态表示方式!
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N][N], v[N], w[N], n, m;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> v[i] >> w[i];
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
f[i][j] = f[i - 1][j]; // 左边集合
if (j >= v[i]) {
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
}
cout << f[n][m] << endl;
return 0;
}习题:洛谷 P1048:采药
参见 01 背包的思考方式!
01 背包的优化
4 10
2 1
3 3
4 5
7 9
12垂直箭头代表不选第 4 种物品,而斜线箭头代表选择第 4 件物品。

滚动数组优化:
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[2][N], v[N], w[N], n, m;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> v[i] >> w[i];
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
f[i & 1][j] = f[(i - 1) & 1][j]; // 左边集合
if (j >= v[i]) {
f[i & 1][j] = max(f[i & 1][j], f[(i - 1) & 1][j - v[i]] + w[i]);
}
}
}
cout << f[n & 1][m] << endl;
return 0;
}降维优化:
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N], v[N], w[N], n, m;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> 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;
}阅读提升:对于 01 背包问题一种使用集合的思考方式:
状态表示:
集合:只从前 $i$ 个物品中选,总体积是 $j$ 的选法集合 属性:只从前 $i$ 个物品中选,总体积是 $j$ 的价值最大值选法集合有哪些呢?
a. 选第 $i$ 件物品 -> $f[i - 1][j]$ b. 不选第 $i$ 件物品 -> $f[i - 1][j - v[i]] + w[i]$状态计算:
完全背包
- 阶段划分:按已处理物品的件数将选择过程划分为
个阶段,设阶段变量 代表前 件物品 - 设计状态:设
表示将前 件物品选择若干件放入体积为 的背包获得的最大价值(体积 可以不装满)。 - 决策:考虑第
件物品选 件。 - 状态转移方程:
- 边界:
- 目标解:
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N][N], v[N], w[N], n, m;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> v[i] >> w[i];
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
for (int k = 0; k * v[i] <= j; k++) {
f[i][j] = max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k);
}
}
}
cout << f[n][m] << endl;
return 0;
}习题:洛谷 P1616 疯狂的采药
参见完全背包的思考方式!
完全背包的优化
考虑进一步优化状态转移方程:
f[i][j] = max(f[i - 1, j], f[i - 1, j - v] + w, f[i - 1, j - 2v] + 2w, f[i - 1, j - 3v] + 3w, ....)f[i][j - v] = max(f[i - 1, j - v], f[i - 1, j - 2v] + w, f[i - 1, j - 3v] + 2w, ....)所以有:
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N][N], v[N], w[N], n, m;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> v[i] >> w[i];
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
f[i][j] = f[i - 1][j];
if (j >= v[i]) f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]);
}
}
cout << f[n][m] << endl;
return 0;
}继续优化:类比 01 背包问题
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N], v[N], w[N], n, m;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> 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]);
}
}
cout << f[m] << endl;
return 0;
}阅读提升:对于完全背包问题一种使用集合的思考方式:
状态表示:
集合:只从前 $i$ 种物品中选,总体积是 $j$ 的选法集合 属性:只从前 $i$ 种物品中选,总体积是 $j$ 的价值最大值选法集合有哪些呢?第
件物品可以选 件, 不是无限大的,有体积 的限制 状态计算:
多重背包 I & II
以完全背包的思路来做多重背包,只需要考虑每种物品的数量即可。时间复杂度:
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N][N], v[N], w[N], s[N], n, m;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> 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 * v[i] <= j && k <= s[i]; k++) { // 考虑数量
f[i][j] = max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k);
}
}
}
cout << f[n][m] << endl;
return 0;
}二进制优化:将多重背包看成01 背包问题。时间复杂度:
#include <bits/stdc++.h>
using namespace std;
const int N = 25000;
int f[N], w[N], v[N];
int main() {
int n, m; cin >> n >> m;
int idx = 0;
for (int i = 0; i < n; i++) {
int a, b, s;
cin >> a >> b >> s;
int k = 1;
while (k <= s) {
idx++;
v[idx] = a * k;
w[idx] = b * k;
s -= k;
k *= 2;
}
if (s > 0) {
idx++;
v[idx] = a * s;
w[idx] = b * s;
}
}
n = idx;
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;
}分组背包
- 阶段划分:按已处理的组数将问题求解过程划分为
个阶段,设阶段变量 代表前 组 - 设计状态:设
表示从前 组物品种选择若干件放入体积为 的背包的最大价值和 - 决策:考虑第
组物品选 件还是选 件,若选 件则需枚举第 组所有物品考虑选哪一件? - 状态转移方程:
- 边界与目标解:
- 边界:
- 目标解:
- 边界:
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int w[N][N], v[N][N], s[N];
int f[N][N];
int main() {
int n, m; cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> s[i];
for (int j = 1; j <= s[i]; j++) {
cin >> v[i][j] >> w[i][j];
}
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
f[i][j] = f[i - 1][j];
for (int k = 1; k <= s[i]; k++) { // 第 i 组物品选第 k 个,只选一个
if (v[i][k] <= j) {
f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
}
}
}
}
cout << f[n][m] << endl;
return 0;
}分组背包的优化
类比 01 背包的优化方式
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int w[N][N], v[N][N], s[N];
int f[N];
int main() {
int n, m; cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> s[i];
for (int j = 1; j <= s[i]; j++) {
cin >> v[i][j] >> w[i][j];
}
}
for (int i = 1; i <= n; i++) {
for (int j = m; j >= 0; j--) { // 体积从大到小循环
for (int k = 1; k <= s[i]; k++) {
if (v[i][k] <= j) {
f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
}
}
}
}
cout << f[m] << endl;
return 0;
}洛谷 P1757:通天之分组背包
方案数问题
AcWing 278. 数字组合
给定
分析:
边界条件:
目标解:
for (int i = 0; i <= n; i++) f[i][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
f[i][j] = f[i - 1][j];
if (j >= a[i]) {
f[i][j] += f[i - 1][j - a[i]];
}
}
}
cout << f[n][m] << endl;请你完成降维优化的工作!
AcWing 279. 自然数拆分
给定一个自然数
注意:
- 拆分方案不考虑顺序;
- 至少拆分成 2 个数的和。
求拆分的方案数 mod 2147483648 的结果。
分析:给定
边界条件:
目标解:
注意:
- 至少拆分成
个数 2147483648爆int
for (int i = 0; i <= n; i++) f[i][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
for (int k = 0; k * i <= j; k++) {
f[i][j] += f[i - 1][j - i * k];
}
}
}
cout << (f[n][n] - 1) % 2147483648 << endl;注:以上代码提交之后会爆空间,请你完成降维优化的工作!
习题
可行性问题
AcWing 280. 陪审团
分析:该题可以看作是具有多个体积维度的01 背包。把
- 人数:每个候选人的人数都是
,填满容积为 的背包 - 辩方得分:即辩方给候选人打的分数
,取值在 - 控方得分:即控方给候选人打的分数
,取值在
设
边界:
目标:找到一个状态
可以将阶段这个维度优化掉。
以上我们可以求出满足条件的辩方总分和控方总分,如果需要输出具体方案,请参考
AcWing 281. 硬币
分析:该题属于多重背包模型,其中物品是硬币,体积为面值,
设
边界:
目标:
注意:直接提交朴素代码会超时(考虑二进制优化),请你完成降维优化工作。
二维费用背包
看电影
新学期到了,朵朵明天要去上学。她决定今晚要玩得开心。她非常喜欢看电影。所以她想让她叔叔买些电影,今晚和她一起看。但是爷爷只给他们
朵朵列了
但是有一个奇怪的问题,店里只卖
1 // Q 组测试数据
3 2 10 // N M L
11 100 // T1 V1
1 2 // T2 V2
9 1 // T3 V3
3 // 输出分析:二维费用背包。本题中,每部电影的选择产生的费用有两个:一是花费了播放时间,二是占用了一部电影的名额。背包的体积也受到两个费用的限制:一是总的播放时间,二是总的电影数量。
设
状态转移方程:
边界:
目标解:
该题可以看作是 01 背包变形,请你完成降维优化的工作!
