【基础】背包九讲之完全背包

今天,我们来讲一讲背包问题中的另一个经典题型——完全背包。

问题化简

有N种物品和一个容量为V的背包。第i种物品的费用是c[i],价值是w[i],每种物品的数量可以任选。求解将哪些物品装入背包可使总价值最大。

 

一个解法

注意到完全背包和01背包的不同之处在于:完全背包每种物品可以无限取,而01背包每种物品只能取一次。

有了01背包的基础,我们定义f[i][j]表示前i种物品,占用了j的空间,所能得到的最大价值。转移的时候,由于每种物品可以取多次,所以我们要枚举当前物品要取多少次,而不是枚举取或不取。

通过分析,转移方程很容易就能得到了:

f[i][j]=max{f[i-1][j-k*c[i]]+k*w[i] | 0<=k*c[i]<=j}

这样求解的时间复杂度最坏情况下是O(NV^2)(每种物品的体积都是1),空间复杂度是O(NV)。

核心代码如下:


for (int i = 1; i <= n; ++i) 
    for (int j = 1; j <= v; ++j) 
        for (int k = 0; k * c[i] <= j; ++k) 
            f[i][j] = max(f[i][j], f[i - 1][j - k * c[i]] + k * w[i]);
int ans = 0;
for (int i = 1; i <= v; ++i)
    ans = max(ans, f[n][i]);

那么,是否还有优化的余地呢?

 

正解驾到

我们类比01背包的优化方法,f[i][j]的值只和f[i-1][0...j]有关,那么很显然可以先把空间复杂度降到O(V)。

时间复杂度应该怎么优化呢?

观察代码可以发现,第三重循环非常令人讨厌(手动滑稽)。我们要想个办法把这重循环优化掉。这里还是要类比到01背包。

先贴上01背包优化后的程序:

for (int i = 1; i <= N; ++i) 
    for (int j = V; j >= 0; --j)
        if (j >= c[i])
            f[j] = max(f[j], f[j - c[i]] + w[i]);
int ans = 0;
for (int j = 0; j <= V; ++j)
    ans = max(ans, f[j]);

 

01背包程序中的第二重循环到倒过来是因为f[j+1]处理后不会影响到f[j]的转移。这么处理是因为计算f[j]的值的时候,f[1...j-1]还没算上第i件物品的贡献。换句话说,就是防止了一件物品被取多次

等等,完全背包不就是要求一个物品可以取多次吗?

所以,我们只要把01背包的第二重循环正过来就能正确解决完全背包的问题了。

代码如下:


for (int i = 1; i <= N; ++i) 
    for (int j = 0; j <= V; ++j)
        if (j >= c[i])
            f[j] = max(f[j], f[j - c[i]] + w[i]);
int ans = 0;
for (int j = 0; j <= V; ++j)
    ans = max(ans, f[j]);

 

总结,完全背包的实现和01背包的实现是相似的,要注意区分。

最后,动态规划最重要的不是代码,而是思想!!!

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值