LintCode798: Backpack VII (多重背包问题 DP经典)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/roufoo/article/details/83088731

多重背包问题可以视为01背包的变形。

解法1:没有经过优化的DP。
dp[i][j] 表示前i件物品在价值不超过j的情况下的最大重量。
若输入为:
8 //n = money
[3,2] //prices
[300,160] //weight
[1,6] //amounts
输出为 640
dp[][] 为:
0 0 0 0 0 0 0 0 0
0 0 0 300 300 300 300 300 300
0 0 160 300 320 460 480 620 640
注意这里不能用贪婪法。比如说先花3元买weight为300的物品,然后剩下5元买两件weight为160的物品。这样重量为620,非最优。原因是这样会剩下1元用不上

时间复杂度O(NW): N是所有item的个数的总和(注意每个item可能有若干个), W是总的money数。
空间复杂度O(nW): n是item种数,W是总的money数。

class Solution {
public:
    /**
     * @param n: the money of you
     * @param prices: the price of rice[i]
     * @param weight: the weight of rice[i]
     * @param amounts: the amount of rice[i] 
     * @return: the maximum weight
     */
    int backPackVII(int n, vector<int> &prices, vector<int> &weight, vector<int> &amounts) {
        int itemCount = prices.size();   //count of items
        vector<vector<int>> dp(itemCount + 1, vector<int>(n + 1, 0)); //dp[i][j] means the maximum weight of first i tems with total price <= j
        
        for (int i = 1; i <= itemCount; ++i) { // 遍历每种物品
            for (int k = 1; k <=n; ++k) {
                dp[i][k] = dp[i - 1][k];
            }

            for (int j = 0; j <= amounts[i - 1]; ++j) { // 对每种物品,遍历其个数
                for (int k = 1; k <= n; ++k) { //k->  1 .. n
                    if (k >= j * prices[i - 1]) {
                        dp[i][k] = max(dp[i][k], dp[i - 1][k - j * prices[i - 1]] + j * weight[i - 1]);
                    }
                }
            }
        }

        return dp[itemCount][n];
    }
};

另外还要注意,上面的三层循环不能这样写:
原因是dp[i][k]被 j 循环和 i 循环改写了,而dp[i][k]应该是独立于 j 循环的。

for (int i = 1; i <= itemCount; ++i) { // i->item[i]
            for (int j = 0; j <= amounts[i - 1]; ++j) { //j-> amount[0]..amount[i-1]
                for (int k = 1; k <= n; k++) { //k->  1 .. n
                    dp[i][k] = dp[i - 1][k];  //应该提出去
                    
                    if (k >= j * prices[i - 1]) {
                        dp[i][k] = max(dp[i][k], dp[i - 1][k - j * prices[i - 1]] + j * weight[i - 1]);
                    }
                }
            }
        }

Solution 2: 在Solution 1的基础上加上一维数组优化。
时间复杂度还是O(NW)。
空间复杂度O(W)。

class Solution {
public:
    /**
     * @param n: the money of you
     * @param prices: the price of rice[i]
     * @param weight: the weight of rice[i]
     * @param amounts: the amount of rice[i] 
     * @return: the maximum weight
     */
    int backPackVII(int n, vector<int> &prices, vector<int> &weight, vector<int> &amounts) {
        int itemCount = prices.size();   //count of items
        vector<int> dp(n + 1, 0);
        for (int i = 1; i <= itemCount; ++i) {
                for (int k = prices[i - 1]; k <= m; ++k) {
                    dp[k] = max(dp[k], dp[k - prices[i - 1]] + weight[i - 1]);
                }
        }
        return dp[n];
    }
};

也可以写成如下形式,这样数组下标从i-1变成了i。

    int backPackVII(int n, vector<int> &prices, vector<int> &weight, vector<int> &amounts) {
        int itemCount = prices.size();   //count of items
        vector<int> dp(n + 1, 0);
        for (int i = 0; i < itemCount; ++i) {
             for (int k = prices[i]; k <= m; ++k) {
                 dp[k] = max(dp[k], dp[k - prices[i]] + weight[i]);
             }
        }
        return dp[n];
    }

注意:
1). 这里不能写成

        for (int i = 1; i <= itemCount; ++i) { 
            for (int j = 1; j <= amounts[i - 1]; ++j) {
                for (int k = n; k >= j * prices[i - 1]; --k) {
                    dp[k] = max(dp[k], dp[k - j*prices[i - 1]] + j*weight[i - 1]);
                }
            }
        }

为什么在Solution 1中我们要加 j*, 而Solution 2就不能加j*呢?
先回顾一下Solution 1和Solution 2的区别:
Solution 1是二维数组。k是从小到大。第i次循环时,对于某个具体的dp[i][k]值,应该从
dp[i-1][k-jprices[i-1]]+jweight[i - 1]), j=0…amounts[i-1]中找。
dp[i-1][]存储的是第i-1次循环的结果。

Solution 2一维数组,k从大到小,若k2<k1,dp[k1]可以参考dp[k2]的值,因为此时dp[k2]还未更新,还是上次i-1循环时的值。所以Solution 2和Solution 1是等价的。

下面分析加j的原因。因为DP算法中,我们每次要为dp[i][k]或dp[k]找一个尽可能大的(这里是求最大值)candidate与dp[i][k]或d[k]比较。
Solution 1里面,k从小到大,所以dp[i][k]能找到的最大candidate就是dp[i - 1][k - j * prices[i - 1]] + j * weight[i - 1])。
Solution 2 里面,k从大到小,所以dp[k]能找到的最大candidate就是dp[k - prices[i - 1]] + weight[i - 1]。也就是说这里j=1。
如果这里用j * prices[i - 1],那么 dp[k - j * prices[i-1]] + j
weight[i-1]就远小于dp[k - prices[i - 1]] + weight[i - 1]了,不能保证最优。

2). 注意这里 j 循环必须是从1开始。因为j 循环实际上就是一个计数而已,如果从0开始就多计算了一次。这样的话dp[k - (amounts[i-1]+1)*prices[i - 1]]并没有意义,因为并没有那么多该物品可以取,它可能是不取别的物品的最优值。

而solution 1中j 循环可以从0开始,也可以从1开始。从0开始无非就是多做了一次

dp[i][k] = max(dp[i][k], dp[i - 1][k]);

而已,因为0跟任何数相乘仍然为0。

猜你喜欢

转载自blog.csdn.net/roufoo/article/details/83088731
今日推荐