5.4 KiB
一、题目描述
有 N
件物品和一个容量是 V
的背包。每件物品只能使用一次。
第 i
件物品的体积是 v_i
,价值是 w_i
。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1…N
。
输入格式
第一行两个整数,N,V
,用空格隔开,分别表示物品数量和背包容积。
接下来有 N
行,每行两个整数 v_i,w_i
,用空格隔开,分别表示第 i
件物品的体积和价值。
输出格式 输出一行,包含若干个用空格隔开的整数,表示最优解中所选物品的编号序列,且该编号序列的字典序最小。
物品编号范围是 1…N
。
数据范围
0<N,V≤1000
0<v_i,w_i≤1000
输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
1 4
二、只能使用二维状态表示
因为 求具体的方案,我们就 不能采取之前滚动数组优化版本的 01
背包,因为这样会损失一些具体方案.
三、如何确保字典序最小?
因为要求字典序最小,那么我们肯定采取贪心策略: 能选序号小的就选序号小的
举个栗子,给定一个原始朴素版本的01
背包数据:
2 3
2 4
2 4
输出答案:
4
这个非常好理解吧:有两个物品,一个体积为3
的背包,每个物品只能选择或不选择,问最终不超过背包体积上限3
时,最大价值是多少?
在原始的版本中,是不强调序号的概念的,最终只要最大值正确就可以,不关心是从哪个序号过来的,比如本题,其实是可以选择1
号物品获取到4
个价值,当然也可以选择2
号物品获取4
个价值。
用下面的代码模拟跑一下:
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m, v[N], w[N];
int f[N][N];
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d %d", &v[i], &w[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]);
}
}
int j = m;
for (int i = n; i >= 1; i--)
if (j >= v[i] && f[i][j] == f[i - 1][j - v[i]] + w[i]) {
printf("%d ", i);
j -= v[i];
}
return 0;
}
输出的答案是:
2
这是什么意思?就是只要选择了序号为2
的物品,就可以达到最大价值,最大价值是4。
Q
:为什么代码输出的是2
号,而不是1
号呢?
我们来研究一下这段代码:
int j = m;
for (int i = n; i >= 1; i--)
if (j >= v[i] && f[i][j] == f[i - 1][j - v[i]] + w[i]) {
printf("%d ", i);
j -= v[i];
}
因为最终的最大值保存在f[n][m]
,如果想知道是怎么到达这个最大值状态的,需要从后向前枚举每个物品,如果剩余空间容量大于等于物品体积,就考查一下目前的最大值是不是由某个减去v_i
的状态转移而来,如果是,就输出这个物品。
联想一下上面的栗子:2,1
都是可以做为答案的,当然从后向前来枚举,每一个遇到的是2
而不是1
,就是 默认第一个遇到的有效,直接把体积减掉,继续向前考虑下一个子问题。这样的策略,肯定是大号在前,小号在后啊~,这样所求的是 字典序最大的。
所以我们应该反一下, 从后往前去遍历所有物品,这样f[1][m]
就是最后答案,那么我们就 从前往后遍历就可以求具体方案,这样求的是字典序最小的。
倒推 状态转移路径 的时候,只能在 分叉转移 的时候,即 当前 物品既可以 选 又可以 不选 时,优先 选
因此,我们本题的 背包DP
需要倒过来(从N
递推到1
)做,然后再 从1
倒推回N
找出路径
这样在抉择时,如果出现 分叉转移,我们就优先 选 当前物品即可
三、实现代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1100;
int f[N][N];
int n, m;
int v[N], w[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 = n; i >= 1; 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]);
}
int j = m;
for (int i = 1; i <= n; i++)
if (j >= v[i] && f[i][j] == (f[i + 1][j - v[i]] + w[i])) {
printf("%d ", i);
j -= v[i];
}
return 0;
}