此文章将对于动态规划的背包问题进行讲解(笔者蒟蒻原创,有差错之处请大佬指正)。首先我们来看背包的基本问题。
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背包来做。
未完待续~~