AcWing
12. 背包问题求具体方案
题目要求输出字典序最小的解,假设存在一个包含第1
个物品的最优解,为了确保字典序最小,必然要选第1
个。那么问题就转化成从2~N
这些物品中找到最优解。一般的f[i][j]
记录的都是前i
个物品,背包容量为j
时的最优解,现在将f[i][j]
定义为从最后一个
元素到第i
个元素,背包容量为j
时的最优解。状态转移方程为:
f[i][j] = max{f[i+1][j], f[i+1][j-v[i]] + w[i]}
这样定义的优势在于,对于第i
个物品,有两种情况:第一种是,在最优价值下,不选第i
个物品,那么最优解等同于从第i+1
个物品到最后一个
物品,背包容量为j
时的最优解;第二种是,在最优价值下,可以选第i
个物品,那么最优解等于当前物品的价值w[i]
加上从第i+1
个物品到最后一个
物品,背包容量为j−v[i]
时的最优解。由于是顺序反过来求dp,我们能拿到由i
到最后一个物品
,在某个容量下的最优利益。
怎么考虑取不取物品i
,例如,第一个物品呢?如果f[2][m-v[1]] + w[1] >= f[2][m]
,说明在第2
到最后一个
物品已经到最高利益(容量m
)的情况下,取第1
个物品能维持这个最高利益,甚至取到更高的利益(此时,即使不考虑字典序,也一定要取了),根据字典序优先原则(主要是针对相等的情况下),就要取物品1
;否则,取第1
个物品不能取得最高利益,不应该取。普遍地,如果f[i+1][j-v[i]] + w[i] >= f[i+1][j] (j>=v[i])
,就要取物品i
;否则不取。
#include <bits/stdc++.h>
using namespace std;
int n, m;
const int N = 1010, M = 1010;
int v[N], w[N];
int f[N][M];
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];
for(int i = n; i >= 1; i--){
for(int j = 1; 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 i = 1, j = m;
while(i <= n){
if(j >= v[i] && f[i + 1][j - v[i]] + w[i] >= f[i + 1][j]){
cout << i << ' ';
j -= v[i];
}
i++;
}
return 0;
}