动态规划的经典问题————背包问题(二)

完全背包

完全背包是01背包的进阶版。
题目是一个体积为V的背包,要装N种物品,每种物品有体积c,价值w两种属性, 要求装入物品总价值最大,与01背包唯一不同的是:每种背包可以取任意数量。

同样,用i表示第i个物品,这个物品的体积为 ci ,价值为 wi ,j表示当前已装入背包的物品总体积, dp[j] 表示背包已装物品总体积为j时的物品价值(不确定是否遍历完全部物品)。

这类题目不能用贪心算法的原因是,并不是价值越高的物品越多越好,必须还要考虑体积因素。

我们来分析一下,虽然每种物品可以取任意数量,但是背包总体积是固定的,体积为 ci 的物品最多装 Vci 个,那么将其视为 Vci 个体积均为 ci ,价值均为 wi 的不同种物品,就可以转化到01背包的问题上来,这种思路和下面多重背包的解法是一致的,与01背包的区别无非是保存物品属性list数组从n个变成了 ni=0Vci 个。

我们有一种更好的解法,其时间复杂度达到了 O(ns) :在01背包的一维数组解法中,我们是这样写的:

    for(int i = 0; i < n; ++i)
        for(int j = V; j >= list[i].c; --j)
            dp[j] = max{dp[j-list[i].c]+list[i].w, dp[j]}; 

即逆序遍历,这是因为任意一件物品仅有放入或者不放入两种结果,我们在讨论是否放入第i个物品时,依据的是是否放入第i-1个物品的结果,第i个物品必须是还没有讨论过的,比如,已经放进去了,就不可能再放一遍。但是我们反过来想,如果当前我们在讨论是否放入第i个物品的时候,第i个物品已经被讨论过了,那不就是意味着第i个物品不仅有放入或者不放入的结果,还有放入1件、放入2件、…、放入 Vci 件的结果。

因此,我们只要将01背包遍历顺序改为正序遍历,就变成了完全背包的解法

    for(int i = 0; i < n; ++i)
        for(int j = list[i].c; j <= V; ++j)
            dp[j] = max{dp[j-list[i].c]+list[i].w, dp[j]}; 

一个简单的优化做法是,当 ci<=cjwi>=wj ,那么物品j就不要考虑了,因为不论什么情况,取物品i都比取物品j好。


多重背包

一个体积为V的背包,要装N种物品,每种物品有数量k,体积c,价值w三种属性, 要求装入物品总价值最大。

与完全背包想比,多重背包就是限制了数量。上文讨论完全背包时也提到过,把 ki 件物品i,看成 ki 件体积均为 ci ,价值均为 wi 的不同种物品,就可以转化到01背包上去。

n个物品变成了 ni=0ki 个物品,算法的时间复杂度变为 O(Sni=0ki) 。可以看出, ni=0ki 越大,算法的时间复杂度越高,我们要想办法压缩这个数量。4个体积为 ci ,价值均为 wi 的物品x,可以看成一个体积为 4ci ,价值为 4wi 的物品y,这样的做法虽然压缩了数量,但是不一定得到最优解,比如原解法中,物品x放入2件的时候才有最优解,这里我们却提供不了2个物品x(已经合并成物品y)。

常用的压缩数量的做法是,用二进制分解k(物品的数量)将其分为若干组,每组视为一个新物品,其每组物品包含的个数是: 1,2,4,...,k2c+1 ,且 k 是满足 k2c+1>0 的最大整数。这样的好处是,任意一种小于等于k的取法,都可以通过这些新物品的组合得到,所以既压缩了数量,又能得到最优解

参考资料:

  • 王道机试指南;
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值