动态规划的背包问题

动态规划的背包问题

此文章将对于动态规划的背包问题进行讲解(笔者蒟蒻原创,有差错之处请大佬指正)。首先我们来看背包的基本问题。

01背包

先看题目:

你有一个容量为M的背包和n件物品。第i件的物品重量是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。

想用贪心?可以,但拿不了满分,为什么?有人证明过贪心并不是最优解。至于为什么,您可以试着证明下。反正笔者不会

下面开始动规:

状态转移方程是f(i,j)=max(f(i-1,j),f(i-1,j-w[i])+v[i])

表示在抉择第i个物品时背包还有j的空间。f(i-1,j)表示不拿此物品,f(i-1,j-w[i])+v[i])表示拿此物品。前面一个好理解,后者我们来分析一波。因为我们要拿此物,所以要在有j-w[i]个空间时来拿物品,然后加上v[i]后与不拿来相比哪个大。(在j-w[i]个空间时我们保存了当时的最优解)请读者认真体会以上文字。

下面我们来用代码实现此状态转移方程:

for(int i=1; i<=n; i++)
        for(int j=1; j<=m; j++) {
            if(j>=w[i])
                f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]);
            else f[i][j]=f[i-1][j];//记得这句
        }

更神奇的是我们可以用一维数组来表示:

for(int i=1; i<=n; i++)
        for(int j=m; j>=w[i]; j--) {//这里是逆序,为了在f[j-w[i]]被覆盖之前调用到它,这里直接循环到w[i]就好了
            f[j]=max(f[j],f[j-w[i]]+v[i]);
        }

这时我们就不需要第一个代码中的那么多if了(请读者自己想为什么,很显然的啦

用一维数组来实现其实有个更高端的名字:滚动数组

完全背包

何为完全背包?不同于01背包的就是每种物品有无限个(想拿多少拿多少)。这要怎么实现?

其实不难,先贴代码:

for(int i=1; i<=n; i++)
        for(int j=w[i]; j<=m; j++) {
            f[j]=max(f[j],f[j-w[i]]+v[i]);
        }

嗯?这不就是01背包吗?认真看看,发现这里的j是正序循环。正序循环就能实现完全背包?是的!

解释一波:正序循环可以保证我们后面调用f[j-w[i]]时是已经装了若干个物品i的状态。下面用二维数组来实现下,读者可以对比来看加深理解。

for(int i=1; i<=n; i++)
    for(int j=w[i]; j<=m; j++) {
        if(w[i]<=j)
            f[i][j]=max(f[i-1][j],f[i][j-w[i]]+v[i]);
        else f[i][j]=f[i-1][j];
    }

注意这里是f(i)(j-w[i])+v[i]而不是f(i-1)(j-w[i])+v[i],因为此时我们是需要在当前已经装了i后还继续装i,而不是只装一个i。而滚动数组就不需要考虑这一点(请读者认真体会)。

多重背包

简单来说就是输入时加一个此物品的数量。最简单的办法就是把每种物品拆成多个用01背包来做。

未完待续~~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值