前几天听学长讲了背包,感觉有点难,于是慢慢琢磨,先是听学长分析,后来自己看崔添翼的背包问题九讲 2.0 beta 1.2 那个PDF,再结合网上的许多大牛写的背包讲解,还有就是题目的练习,终于有点体会了。
常见的几类背包问题
1、01背包
题目 有 N 件物品和一个容量为 V 的背包。放入第 i 件物品耗费的费用是 Ci1,得到的 价值是 Wi。求解将哪些物品装入背包可使价值总和最大。
思路,动态规划
特点:每件东西只有一个,那就是说我们可以选放或不放。
用子问题定义状态:即F[i,v]由此我们从一个容量为v的背包可以获得最大价值。那么其状态转移方程就是
F[ i, v]=max{ F [ i-1,v] ,F [ i-1, v-Ci ]+Wi }
这个方程衍生出来的许多背包问题相关的方程,这个方程的意思就是说只考虑第 i 件物品的策略(放或不放),那么就可以转化为一个只和前 i−1 件物品相关 的问题。
1、如果不放第 i 件物品,那么问题就转化为“前 i−1 件物品放入容量为 v 的背 包中”,价值为 F[i−1,v];
2、如果放第 i 件物品,那么问题就转化为“前 i−1 件物品放 入剩下的容量为 v−Ci 的背包中”,此时能获得的最大价值就是 F[i−1,v−Ci] 再加上 通过放入第 i 件物品获得的价值 Wi。
伪代码如下:
F[0,0..V ] ← 0
for i ← 1 to N
for v ← Ci to V
F[i,v] ←max{F[i−1,v],F[i−1,v−Ci] + Wi}
可以参考一份图表:来源:http://blog.csdn.net/mu399/article/details/7722810
博主真是辛苦了
这张图很好的解释了dp过程是个什么样子的。ps:这图的顺序是从e开始装的,倒着装的,可能是作者书写的原因吧。
***************优化空间复杂度***********************
以上方法的时间和空间复杂度均为 O(V N),其中时间复杂度应该已经不能再优化 了,但空间复杂度却可以优化到 O(V )。
关键在于怎么将二维数组变成一维数组。我们可以发现我们的第二层循环是从Ci到V循环,那么我们也就发现对于第i件物品我们是不是可以从V往Ci递减过去,因为由状态转移方程我们知道对于第i件物品,它在V这点的最大价值并不和Ci到V之间的值会相互影响。只和第i-1件物品的选择有关。
到这里,我们就知道可以去掉前面的一维空间了。这样就优化了空间复杂度。只是这样我们到最后也并不知道前i-1件物品用V的背包装最多装多大的价值。(根据题目的需要可以选择性优化空间)
伪代码:
F[0..V ]←0
for i ← 1 to N
for v ← V to Ci
F[v] ←max{F[v],F[v−Ci] + Wi}
******************初始化的细节问题******************
上面的代码我们都初始化为0;那么是不是都是这样呢,很显然不是。
举个简单的例子,当我们求最小值的时候,除了dp[0]初始化为0.其他的都需要初始化 ∞ 。所以初始化则是需要根据题目的要求来选择。
2、多重背包问题
题目
有 N 种物品和一个容量为 V 的背包。第 i 种物品最多有 Mi 件可用,每件耗费的 空间是 Ci,价值是 Wi。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超 过背包容量,且价值总和最大。
思路:
唯一与01背包问题不一样的地方就是同一件物品它有Mi件,那我们就想把它变成01背包来处理,那么,我们是不是可以将其看成取0件,取1件。。。。取m件。
那么我们就有状态转移方程
F[i,v] = max{F[i−1,v−k∗Ci] + k∗Wi |0 ≤ k ≤ Mi}
复杂度是 O(V ΣMi)。
这么高的复杂度在做题的时候当然是不允许的,那么我们就需要降低复杂度,想一想我们是不是可以像其他的一些算法一样采用二进制的思想。
方法是:将第 i 种物品分成若干件 01 背包中的物品,其中每件物品有一个系 数。这件物品的费用和价值均是原来的费用和价值乘以这个系数。令这些系数分别为 1,2,2 ...2k−1,Mi −2k +1,且 k 是满足 Mi −2k +1 > 0 的最大整数。例如,如果 Mi 为 13,则相应的 k = 3,这种最多取 13 件的物品应被分成系数分别为 1,2,4,6 的四件 物品。
分成的这几件物品的系数和为 Mi,表明不可能取多于 Mi 件的第 i 种物品。
另外 这种方法也能保证对于 0...Mi 间的每一个整数,均可以用若干个系数的和表示
这样就将第 i 种物品分成了 O(logMi) 种物品,将原问题转化为了复杂度为 O(V ΣlogMi) 的 01 背包问题,是很大的改进。
下面给出 O(logM) 时间处理一件多重背包中物品的过程:
def MultiplePack(F,C,W,M)
if C ·M ≥ V
CompletePack(F,C,W)
return
k ← 1
while k < M
ZeroOnePack(kC,kW)
M ←M −k
k ← 2k
ZeroOnePack(C ·M,W ·M)
最好模拟一遍。
*********************可行性问题O(VN)的算法**************
这个算法是针对“每种有若干件的物品能否填满给定容量的背包“的问题
在这里就不扩充这个算法。如果想知道的话参考楼天成的“男人八题”的幻灯片
3、完全背包问题
题目 有 N 种物品和一个容量为 V 的背包,每种物品都有无限件可用。放入第 i 种物品 的费用是 Ci,价值是 Wi。求解:将哪些物品装入背包,可使这些物品的耗费的费用总 和不超过背包容量,且价值总和最大。
思路:
类比多重背包:(因为重点不在这)
状态转移方程
F[i,v] = max{F[i−1,v−kCi] + kWi |0 ≤ kCi ≤ v}
重点就是在于改算法的优化成O(VN )的复杂度
这个算法使用一维数组,先看伪代码:
F[0..V ]←0
for i ← 1 to N
for v ← Ci to V
F[v] ←max(F[v],F[v−Ci] + Wi)
重点来了,你很惊讶的发现和01背包只有v的循环次序不同。
为什么这个算法就可行呢?首先想想为什么 01 背包中要按照 v 递减的次序来循环。
让 v 递减是为了保证第 i 次循环中的状态 F[i,v] 是由状态 F[i−1,v −Ci] 递推而来。
换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第 i 件物品”这件策 略时,依据的是一个绝无已经选入第 i 件物品的子结果 F[i−1,v−Ci]。
而现在完全背 包的特点恰是每种物品可选无限件,所以在考虑“加选一件第 i 种物品”这种策略时, 却正需要一个可能已选入第 i 种物品的子结果 F[i,v−Ci],所以就可以并且必须采用 v 递增的顺序循环。这就是这个简单的程序为何成立的道理。
这个算法也可以由另外的思路得出。例如,将基本思路中求解 F[i,v−Ci] 的状态转 移方程显式地写出来,代入原方程中,会发现该方程可以等价地变形成这种形式:
F[i,v] = max(F[i−1,v],F[i,v−Ci] + Wi)!!!
没有像多重背包去乘它的系数
4、混合背包问题
当你理解了前面三种背包就不用看这个问题了,如果你混合背包不懂,返回去去看前面三种基本的背包问题吧
5、二维费用的背包问题
问题
二维费用的背包问题是指:对于每件物品,具有两种不同的费用,选择这件物品必 须同时付出这两种费用。对于每种费用都有一个可付出的最大值(背包容量)。问怎样 选择物品可以得到最大的价值。 设第 i 件物品所需的两种费用分别为 Ci 和 Di。两种费用可付出的最大值(也即两 种背包容量)分别为 V 和 U。物品的价值为 Wi。
思路:
算法 费用加了一维,只需状态也加一维即可。设 F[i,v,u] 表示前 i 件物品付出两种费用 分别为 v 和 u 时可获得的最大价值。状态转移方程就是:
F[i,v,u] = max{F[i−1,v,u],F[i−1,v−Ci,u−Di] +Wi}
如前述优化空间复杂度的方法,可以只使用二维的数组:当每件物品只可以取一次 时变量 v 和 u 采用逆序的循环,当物品有如完全背包问题时采用顺序的循环,当物品 有如多重背包问题时拆分物品。
***********************物品总个数的限制 ***************
有时,“二维费用”的条件是以这样一种隐含的方式给出的:最多只能取 U 件物品。 这事实上相当于每件物品多了一种“件数”的费用,每个物品的件数费用均为 1,可以 付出的最大件数费用为 U。换句话说,设 F[v,u] 表示付出费用 v、最多选 u 件时可得 到的最大价值,则根据物品的类型(01、完全、多重)用不同的方法循环更新,最后在 f[0...V,0...U] 范围内寻找答案!
**************************************背包九讲常见的就这5种背包问题(ps:剩下的四种学长说不常见,出现就会比较难了,O(∩_∩)O哈哈~)***********************
很多小伙伴会问不同题目的动态方程怎么折腾出来,这只能是刷题了,当你问这个问题的时候,肯定和我一样刚入门dp,题目做的多了,方程基本就回来,套路嘛~
资料来源,崔添翼的背包九讲+个人心得
ps:01背包的那张图我一直觉得很棒!谢谢博主了