01背包滚动数组的简单理解

dp碎碎念

前言:觉得二进制很NB,想学状态压缩,可是不会动态规划,连背包都不会的那种,从头开始吧!

一.最最单纯的01背包

0.问题:给定一组物品,每种物品都有自己的重量wi(i=1,2,3…n)和价格vi(i=1,2,3…,n),在限定的总重量(W)内,我们如何选择,才能使得物品的总价格最高。
1.初始化:我们定义dp[i][j]表示在背包容量为j时从0到编号为i的物品所能装进背包的最大价值,从最简单的情况开始,dp[i][0] (](i=0,1,…,n-1)表示在背包容量为0时前i个物品所能装进背包的最大价值,很明显全部的值都是0,因为此时无法装下任何物品.

for(int i=1;i<=n;i++)
dp[i][0]=0;

接着是dp[1][j] (j=0,1,2,…,W),表示在背包容量为0时编号为1的物品所能装进背包的最大价值,如果w1<=j,那么第1个物品可以装下,否则背包仍为空。注意这时候的j并不是W,也就是我们假设存在另外的背包,体积分别为0,1,2,…,W。

for(int j=0;j<=W;j++){
    if(w[1]<=j)dp[1][j]=v[1];
    else dp[1][j]=0;
}

3.对于任意一个物品i,它只有取和不取两种选择,如果它没有被取,那当前背包内物品的最大价值和dp[i-1][j]是一样的,相当于dp[i][j](表示在背包容量为j时前i个物品所能装进背包的最大价值)==dp[i-1][j] (表示在背包容量为j时前i-1个物品所能装进背包的最大价值),因为根本没有添加物品,价值不会增加也不会减少。

随便问一个dp[i][j]是多少,我们知道它表示在背包容量为j时前i个物品所能装进背包的最大价值。如果第i个物品没有被装进去,那么dp[i][j]=dp[i-1][j],如果第i个被装进去,dp[i][j]=dp[i-1][j-w[i]]+v[i],表示在背包容量为j时前i个物品所能装进背包的最大价值为在背包容量为i-1时前j-w[i]个物品所能装进背包的最大价值再加上第i个物品本身的价值。注意现在dp[i-1][j-w[i]]并不是表示把第i-1个物品去掉,而是这个状态的最大价值,它可以是有i-1或没有i-1个物品的!不会吧不会有人真的这么想吧好吧之前的我。

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

4.如果题目只需要最终背包容量为W时前n个物品所能装进背包的最大价值,那么明显dp[小于n][任何数] 都是不需要的,怎么省下这些空间呢?对于上面的空间复杂度为O(n*W)的数组,我们观察发现,dp[i][j] 的产生除了题目给定的w[i],v[i],就只有dp[i-1][k] (k=0,1,2,…j)。但是dp[小于i][任何数]都不是我需要的。我们用神奇的滚动数组来降低空间复杂度。

for(int i=1;i<=n;i++){
    for(int j=W;j>=0;j--)
        DP[j]=max(DP[j],DP[j-w[i]]+v[i]);
}

证明正确性:
如果第一层循环走到了i-1,那么我们可以仍然把数组看成是dp[i-1][…],从dp[i-1][W],一直更新到dp[i-1][0]。然后我们让DP[j] (j=W,W-1,…,2,1,0)=dp[i-1][j] (j=W,W-1,…,2,1,0) 第i-1层走完之后到了第i层,那么现在开始来更新第一个数dp[i][W]了。它会等于max(dp[i-1][W],dp[i-1][W-w[i]+v[i]),也就是上一层的答案全部被保留下来被第i层使用了。

dp[i][W]=max(dp[i-1][W],dp[i-1][W-w[i]]+v[i])
=max(DP[W-w[i]],DP[W-w[i]]+v[i])

,在这个时候我们顺便把DP[W]更新成dp[i][W], 然后重复重复直到DP[0]=dp[i][0],最后DP[j] (j=W,W-1,…,2,1,0)=dp[i][j] (j=W,W-1,…,2,1,0) 这样子整个DP数组都是dp[i][…]的,和dp[i-1][…]都完全无关了,我们就安安心心继续去搞DP[j+1],DP[j+2]了。

接着还有一个小问题:为什么遍历j的时候会反向遍历呢?如果还是正向遍历,那么先用二维的dp观察一下:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]) //(*式),
翻译成DP数组就是:
DP[j]=max(DP[j],DP[j-w[i]]+v[i])//(#式)
到了后面某一个J=j+w[i]的时候,我们想要的是:
dp[i][J]=max(dp[i-1][J],dp[i-1][j]+v[i])
用DP表示:
DP[J]=max(DP[J],DP[J-w[i]]+v[i])=max(DP[J],DP[j]+v[i])
可是!你发现了吗?在本来和(*式)作用相同的(#式)中,我们把DP[j]更换成了dp[i][j]的意义,替换了原来的dp[i-1][j]的意义,相当于原来的dp[i-1][j]的值都被dp[i][j]覆盖了,等dp[i][J]要用的时候它只能用dp[i][j]去更新自己 但它要的是dp[i-1][j]啊。所以这个数组就全乱套了。

反向遍历更新有什么好处?
DP[J]=max(DP[J],DP[J-w[i]]+v[i]),即
dp[i][J]=max(dp[i-1][J],dp[i-1][J-w[i]]+v[i]),我们先更新大的DP[J],它捷足先登把比它小的代表dp[i-1][J]含义的DP[j]给用了再说,现在DP[J]被更新成dp[i][J]的含义也没有任何关系,因为等一下只有比它小的jj需要被更新,那个时候绝对用不着它了,因为
DP[jj]=max(DP[jj],DP[jj-w[i]]+v[i]),DP[jj]用到的DP[jj-w[i]]的jj-w[i]一定是比J小的(无聊的说明)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值