AcWing_12 背包问题求具体方案(模板)

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;
}