0
点赞
收藏
分享

微信扫一扫

背包问题【01背包】

小迁不秃头 2022-02-19 阅读 105
01背包

问题:给定n中物品和一个容量为c的背包,物品i的重量的w[i],其价值为v[i]。问:如何选择装入背包的物品,使得装入背包中的物品的总价值最大?
分析:面对物品,我们只有选择拿与不拿两种选择,不能选择装入某物品的一部分,也不能装入同一物品多次。
解释:为什么叫 01背包 ?拿0、不拿1。

先声明一个大小为 dp[i][j] 数组,表示从下标为 [0-i] 的物品里任意取,放进容量为j的背包,价值总和最大是多少。
注:i 表示第几个物品,j 表示背包的总容量。

状态转移方程:
在这里插入图片描述
状态转移表:
在这里插入图片描述
代码:

#include <iostream>
using namespace std;
int main() {
    /**
     * dp[i][j]: 状态转移方程
     * w[]: 每个物品的重量
     * v[]: 每件商品对应的价值
     */
    int dp[30][200];
    int w[30], v[30];
    int n, m; //n:商品数量,m:背包容量
    cin >> n >> m;
    //初始值从0开始下标为负值
    for (int i = 1; i <= n; ++i) {
        cin >> w[i] >> v[i];
    }
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            if (j < w[i])
                dp[i][j] = dp[i-1][j];
            else //j>=w[i]
                dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
        }
    }
    //=========打印状态转移表===========
    for (int i = 0; i <=n; ++i) {
        for (int j = 0; j <=m; ++j) {
            cout << dp[i][j] << "  ";
        }
        cout << endl;
    }
    cout<<"max value:" << dp[n][m];
    return 0;
}

滚动数组改进:
因为当前状态只与上一个状态有关有关,所以我们可以使用滚动数组。
每当 j 值更新一遍 dp[j] 就滚动更新一遍,所以把 dp[j] 称为 滚动数组

int dp[205], w[35], v[35];

int main() {

    int n, m; //n:商品数量,m:背包容量
    cin >> n >> m;

    for (int i = 1; i <= n; ++i) {
        cin >> w[i] >> v[i];
    }
    for (int i = 1; i <= n; ++i) {
        for (int j = m; j >= 1; --j) { //???此处为什么逆序循环??? 
            if (j >= w[i])
                dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
        }
        //打印状态转移表
        for (int k = 0; k <=m; ++k)
            cout << dp[k] << " ";
        cout << endl;
    }

    cout << endl << "max value:" << dp[m];
    return 0;
}

对逆序循环解析:
本质:后无效性原则,即当前的状态只与上一个状态有关。

dp[j] = max(dp[j],dp[j-w[i]]+v[i]);

解释:如果 j 是顺序循环,dp[j-w[i]] 会先于 dp[j] 更新,也就是 用新值dp[j-w[i]] 去更新 dp[j] 所以出错。j 如果是逆序循环,dp[j] 先与 dp[j-w[i]] 更新,也就是 用旧值 dp[j-w[i]] 更新 dp[j] ,相当于用上一行的 dp[j-w[i]] 更新 f[j] 正确。

举报

相关推荐

0 条评论