背包问题也是动态规划中一个很经典的问题
其问题主要框架为:有一个体积为V的背包(花费上限),有n件物品,第i件物品的体积为v[i],价值为w[i],问怎么放的最大价值。
当然,不同的题会对物品有不一样的限制,比如对物品数量的限制,对物品关系的限制,因此就有了不同种类的背包问题。
一,01背包
问题:有一个体积为V的背包,有n件物品,第i件物品的体积为v[i],价值为w[i],每件物品至多选择一次,问在不超过背包体积的前提下能获得的最大价值。
这是背包型动态规划的基础,有些背包型动态规划可以转化为01背包,从而起到优化的效果。
特点是对于第i件物品,我们只有两种选择,第一种,将这件物品放入背包,第二种,不放这种物品,显然,我们想要在当前体积限制下获得最大的价值,如果放入这件物品
会使总的价值增大,我们就放入这件物品,否则不放。
状态转移方程 dp[i][v]=max(dp[i-1][v],dp[i-1][v-w[i]+c[i]]);
1 for(int i=1;i<=n;i++) 2 for(int v=1;v<=vmax,v++) 3 { 4 if(w[i]>v) dp[i][v]=dp[i-1][v]; 5 else dp[i][v]=max(dp[i-1][v],dp[i-1][v-w[i]]+c[i]); 6 }
我们可以通过数组的重复利用优化掉一维,从而在空间上优化了01背包。
注意一维的01背包的体积需要倒叙枚举,因为每个物品只能使用一次,如果正序枚举,可能导致某一个体积的最大价值是由这件物品已经更新过的最大价值转移而来,从而导致
了重复放该件物品。
1 for(int i=1;i<=n;i++) 2 for(int v=vmax;v>=w[i];v--) 3 dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
二,完全背包
问题:有一个体积为V的背包,有n件物品,第i件物品的体积为v[i],价值为w[i],每件物品至多选择无数,问在不超过背包体积的前提下能获得的最大价值。
我们刚刚在将01背包从二维优化到一维的时候讨论过体积必须倒叙枚举的问题,为什么呢,因为为了防止重复放,而完全背包允许重复放,所以改成正序枚举即可。
状态转移方程 dp[i][v]=max(dp[i-1][v],dp[i][v-w[i]]+c[i]);
1 for(int i=1;i<=n;i++) 2 for(int v=w[i];v<=vmax;v++) 3 dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
一个贪心的小技巧:因为完全背包对于物品数没有限制,所以对于某两件物品i,j,如果w[i]<=w[j],c[i]>=c[j];这样的物品j我们可以丢掉,因为显然i物品比较物美价廉,
在任何情况下,选择i物品都要优于选择j物品,因为它的花费小,奉献的价值还更大,对于随即数据,这种优化方法很有效。
三,多重背包
问题:有一个体积为V的背包,有n件物品,第i件物品的体积为v[i],价值为w[i],件数为t[i],问在不超过背包体积的前提下能获得的最大价值。
首先想一个暴力的做法,我们更改一下01背包,只需要再加一层循环枚举一下件数,是不是就可以解决这个问题。
暴力的状态转移方程:dp[i][v]=max(dp[i-1][v],dp[i-1][v-w[i]*k]+k*c[i],1<=k<=t[i]);
1 for(int i=1;i<=n;i++) 2 for(int v=1;v<=vmax;v++) 3 { 4 if(w[i]>v) dp[i][v]=dp[i-1][v]; 5 else 6 { 7 for(int k=1;k<=t[i];k++) 8 dp[i][v]=max(dp[i-1][v],dp[i-w[i]*k]+k*c[i]); 9 } 10 }
同样,这种写法也可以优化成一维
1 for(int i=1;i<=n;i++) 2 for(int v=vmax;v>=0;v--) 3 for(int k=1;k<=t[i]&&k*w[i]<=vmax;k++) 4 dp[v]=max(dp[v],dp[v-w[i]*k]+c[i]*k);
因为多了一层循环,所以在时间复杂度上,这种暴力的多重背包必然不优,我们可以用两种方法来优化多重背包。
1,二进制优化