lintcode:125 背包问题 II

题目来源

题目描述

在这里插入图片描述

在这里插入图片描述

题目解析

(1)状态:

  • dp[i][j] 表示前i个物品 放入 容量为 j 的背包,最多可以获得的价值
  • a[…]为每个物品的大小,v为每个物品的价值

(2)方程

  • dp[i][j] =
    • j > = a [ i − 1 ] j >= a[i - 1] j>=a[i1],即空间够放下当前物品(只需要考虑当前物品),有两种决策:
      • 不放,那么 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i - 1][j] dp[i][j]=dp[i1][j]
      • 放,那么: d p [ i ] [ j ] = d p [ i − 1 ] [ j − a [ i − 1 ] ] + v [ i − 1 ] ] dp[i][j] = dp[i - 1][j - a[i - 1]] + v[i - 1]] dp[i][j]=dp[i1][ja[i1]]+v[i1]]
      • 从上面两个决策中选择一个:max{ d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − a [ i − 1 ] ] + v [ i − 1 ] ] dp[i - 1][j], dp[i - 1][j - a[i - 1]] + v[i - 1]] dp[i1][j],dp[i1][ja[i1]]+v[i1]]}
    • j < a [ i − 1 ] j < a[i - 1] j<a[i1], 即空间不足以放下第 i 件物品,那么:
      • d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i - 1][j] dp[i][j]=dp[i1][j]
  • 为什么是 a[i - 1]] v[i - 1] ?
    • 因为 第 i 件(最后一件物品)物品的下标为 i - 1

(3)初始化

  • dp[i][0] = 0
  • dp[0][j] = 0

(4)答案

  • dp[n][m]
class Solution {
public:
    int backPackII(int m, vector<int> &a, vector<int> &v) {
        int n = a.size();
        if(m == 0 || n == 0){
            return 0;
        }

        // 表示前i个物品装入容量为j的背包的最大价值
        std::vector<std::vector<int>> dp(n + 1, std::vector<int>(m + 1, 0));
        for (int i = 0; i < n + 1; ++i) {
            for (int j = 0; j < m + 1; ++j) {
                if(i == 0 || j == 0){ // 前0个物品 || 背包承重为0时
                    dp[i][j] = 0; //最大价值为0(不要求刚刚装满背包)
                }else if(j - a[i - 1] >= 0){ // 当前物品重量 能 放入背包j中
                    dp[i][j] = std::max(
                            dp[i - 1][j],  // 不装:那么就是前i-1个物品装入容量为j的背包中的最大价值
                            dp[i - 1][j - a[i - 1]] + v[i - 1]   // 装: 前i-1个物品装入容量为j - a[i - 1]的背包中的最大价值 + 当前物品的价值
                            );
                }else{
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }

  
        return dp[n][m];
    }
};




背包问题

需要同时考虑两个维度:重量 + 价值。

  • 所有的背包问题,先从重量入手,已经知道一个背包最大承重是M,不管怎么做装,每个装物品的方案的总重量都是0-M
  • 如果对于每个总重量,我们能知道对应的最大价值是多少

(1)确定状态

  • 需要知道N个物品
    • 是否能拼出重量W(W = 0…M)
    • 对于每个重量W,最大总价值是多少?
  • 最后一步:最后一个物品(重量 A N − 1 A_{N-1} AN1,价值 V N − 1 V_{N-1} VN1)是否能装入背包
    • 情况一:(能够装入但是/不能够装入)不进入,最大总价值不变。
    • 情况二:(能够装入而且)选择装入,那么进入后,价值变为 V + V N − 1 V+V_{N-1} V+VN1
    • 上面两个决策中选择一个价值最大的
  • 子问题:
    • 要求前N个物品能不能拼出0,1,…M,以及拼出重量W能获得的最大价值
    • 先要求前N-1个物品能不能拼出0,1,…M,以及拼出重量W能获得的最大价值
  • 状态:
    • f[i][j] 表示前 i 个物品装入大小为 j 的背包里, 可以获取的最大价值总和.

(2)转移方程

在这里插入图片描述

(3)初始条件&&边界情况

  • 初始条件:
    • f[0][0] = 0。0个物品可以拼出重量0,最大总价值是0
    • f[0][1…M] = -1:0个物品不能拼出大于0的重量
  • 边界情况:
    在这里插入图片描述

(4)计算顺序

  • 从小到大,从上到下
  • 答案:dp数组中最大的那一个

滚动数组:

class Solution {
public:
    int backPackII(int m, vector<int> &A, vector<int> &V) {
        int n = A.size();
        if(n == 0){
            return 0;
        }

        std::vector<int> f (m + 1 , 0);
        int i , w ;
        f[0] = 0;
        for (i = 1; i <= m; ++i) {
            f[i] = -1;
        }

        for (i = 1; i <= n; ++i) {  //对于每一个物品
            for (w = m;  w >=  0; --w) {  //对于每一个重量
                if(w >= A[i-1] && f[w - A[i - 1]] != -1){  //装得下&&之前有方案
                    f[w] = std::max(f[w], f[w - A[i - 1]] + V[i - 1]);
                }
            }
        }
        
        int res = 0;
        for (i = 0; i <= m; ++i) {
            if(f[i] != -1){
                res = std::max(res, f[i]);
            }
        }
        return res;
    }
};

三种背包总结

背包问题状态转移方程的核心是:

  • 分类 要第i件物品 与 不要第i件物品 两种情况
  • 若可以挑多件,那就是分挑 0,1,2, …, k件那么多种情况。
    • 01背包问题:
      • N个物品,背包大小为B,每种物品仅有一件,可以取或者不取,第 i i i件物品的费用是 C i C_i Ci,价值是 W i W_i Wi
      • 将哪些物品装入背包,可以使得这些物品耗费的费用总和不超过背包容量,而且价值总和最大
    • 完全背包问题:
      • N个物品,背包大小为B,每种物品无限件,可以取或者不取,第 i i i件物品的费用是 C i C_i Ci,价值是 W i W_i Wi
      • 将哪些物品装入背包,可以使得这些物品耗费的费用总和不超过背包容量,而且价值总和最大
    • 多重背包问题:
      • N个物品,背包大小为B,每种物品最多有 M i M_i Mi,可以取或者不取,第 i i i件物品的费用是 C i C_i Ci,价值是 W i W_i Wi
      • 将哪些物品装入背包,可以使得这些物品耗费的费用总和不超过背包容量,而且价值总和最大
class Solution {
    //  01背包 Time ~ O(V)
    // dp为动态数组,B为背包总大小,C为当前背包价值,W为当前背包重量
    void zeroOnePack(vector<int> &dp, int B, int C, int W) {
        for (int j = B; j >= C; --j) {  // 对于每一个背包重量
            dp[j] = std::max(dp[j], dp[j - C] + W);  // 求最大价值  [不装, 装]
            // dp[j] = dp[j] + dp[j - C];  // 求方案总数
        }
    }

    // 完全背包(快) Time ~ O(V)
    void completePack(vector<int> &dp, int B, int C, int W) {
        for (int j = B; j >= C; --j) {  // 对于每一个背包重量
            dp[j] = std::max(dp[j], dp[j - C] + W);
            // dp[j] = dp[j] + dp[j - C];  // 求方案总数
        }
    }

    // 完全背包(慢)Time ~ O(V/C * V) 仅用来与快版本进行比较
    void completePack2(vector<int> &dp, int B, int C, int W) {
        for (int k = 0; k * C <= B; k++) {  // 对于当前物品,能放入[0, 1, 2... k]件
            zeroOnePack(dp, B, k * C, k * W);  // 没一件可以转为01背包问题
        }
    }

    // 多重背包(快) Time ~ O(logM * V)
    void multiplePack(vector<int> &dp, int B, int C, int W, int M) {
        int k = 1;
        while (M > k) {
            zeroOnePack(dp, B, k * C, k * W);
            M = M - k;
            k <<= 1;
        }

        zeroOnePack(dp, B, M * C, M * W);
    }



    // 多重背包(慢) Time ~ O(k * V) 仅用来与快版本进行比较
    void multiplePack2(vector<int> &dp, int B, int C, int W, int M){
        for (int k = 0; k <= M; k++) {
            zeroOnePack(dp, B, k * C, k * W);
        }
    }

    // -------

    // 二维 01背包 Time ~ O(U * V)
    // B为背包总承重
    void zeroOnePack2D(vector<vector<int>> &dp,  int B, int U, int C1, int C2, int W){
        for (int j = B; j >= C1; --j) {
            for (int k = U; k >= C2; --k) {
                dp[j][k] = std::max(dp[j][k], dp[j - C1][k - C2] + W);
            }
        }
    }

    // 二维 完全背包 Time ~ O(U * V)
    void completePack2D(vector<vector<int>> &dp,  int B, int U, int C1, int C2, int W){
        for (int j = C1; j <= B; j++) {
            for (int k = C2; k <= U; k++) {
                dp[j][k] = std::max(dp[j][k], dp[j - C1][k - C2] + W);
            }
        }
    }



public:

    // C为cost,W为重量weight、M为件数
    int backPackII(int N, int B, vector<int> &C, vector<int> &W, vector<int> &M) {
        // N为物品个数,B为背包总大小
        std::vector<int> dp(B + 1);

        // 初始化方式一:不要求刚好装满背包
        for (int j = 0; j <= B; ++j) {
            dp[j] = 0;
        }

        // 初始化方式二:要求刚好装满背包
        dp[0] = 0;
        for (int j = 1; j <= B; ++j) {
            dp[j] = -1;
        }

        // 迭代计算dp
        for (int i = 0; i <= N; ++i) {  // 对于每一个物品
            if (第i件物品属于01背包) {
                zeroOnePack(dp, B, C[i - 1], W[i - 1]);
            } else if (第i件物品属于完全背包) {
                completePack(dp, B, C[i - 1], W[i - 1]);
            } else if (第i件物品属于多重背包) {
                multiplePack(dp, B, C[i - 1], W[i - 1], M[i - 1]);
            }
        }


        // 返回最大价值
        return dp[V];
    }
};

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值