动态规划之背包问题
参考《背包问题九讲》
问题描述:
给定一个容量为N的背包,给定m件物品,每件物品价值为w[i]、费用为c[i],求如何选择物体可以使物体的费用不超过背包的总容量,且背包能够带走的最大的价值。
1. 如果限定每件物品只有一件,则为0-1背包问题;
2. 如果每件物品的数量不受限制,则为完全背包问题;
3. 如果限定每件物品的数量为n[i]件,则为多重背包问题;
4. 将1、2和3的情况融合,即,有的物品只有一件、有的物品数量不受限制、有的数量为n[i]件,则为混合背包问题;
5. 当选择一件物品必须同时付出两种代价v[i]和u[i]时,是多重费用背包问题;
6. 当物体之间有冲突,某几个物体中只能选择一个时,为分组背包问题;
7. 当选择A物体的前提是必须选择B物体,则为有依赖的背包问题
思路
背包问题属于典型的动态规划问题,需要使用状态转移方程求解,必须理解状态转移方程的含义。在下面求解过程中,将各个问题进行抽象,以伪代码的形式给出解答。
1. 0-1背包问题:
f[i][v]表示:对于容量为v的背包,在给定物体中只考虑前i件物品,所能获得的最大总价值。
状态转移方程:
f[i][v] = max{f[i-1][v], f[i-1][v-c[i]]+w[i]}
这种解法需要维护一个m*N的二维数组,然后从上到下、从左到右地建立数组,最终数组右下角的值既为能带走的最大value。
伪代码:
for irow = 1 <-- bagsize
for jobj = 1 <-- object_number
if cost[jobj] > irow:
# bag cannot hold the object
else:
# bag can hold the object
代码实现:GitHub。
我们可以将空间复杂度可以从O(mxN)优化为O(N),即使用一维数组求解0-1背包问题,在每次地推f[i][v]时,让v由大到小变化即可。f[v]含义为:(设处理数组第i个元素)尺寸为i的背包能够带走的最大价值
伪代码:
zero_one_package(cost, worth, bagsize,f):
for v = bagsize <-- 0
f[v] = max{f[v-cost]+worth, f[v]}
for i = 1 <-- object_num
zero_one_package(c[i], w[i], bagsize, f)
代码实现:GitHub。
0-1背包优化思路:
idea1:对于循环for v = bagsize <-- 0
,可以优化下界:for v = bagsize <-- cost[i]
,即当cost[i]超过bagsize时,此物体必然不能装入bag
2. 完全背包
思路1:由于每件物体的数量不受限制,我们可以采用二进制的思想将一件物品变换为等价的k件物品,它们的cost和value分别为n*c和n*value,n = 2^{k}, k=0,1,..,[log2(n/w)]。不论最终对此件物品选择多少件,都可以等价于在拆分过的物体集合上求解0-1背包问题。
思路2:基于0-1背包的求解思路,递推f[v]时,让bagsize由小到大的变化.
伪代码:
complete_package(cost, worth, bagsize,f):
for v = 0 <-- bagsize
f[v] = max{f[v-cost]+worth, f[v]}
for i = 1 <-- object_num
zero_one_package(c[i], w[i], bagsize, f)
代码实现:GitHub
3. 多重背包
记第i种物品有n[i]件,则对于第i种物品共有n[i]+1个选择策略。多重背包问题与完全背包问题类似,状态转换方程可以写为:
f[i][v] = max{f[i-1][v-k*c[i]] + k*w[i]}, 0 <= i <=n[i]
即,遍历特定物体所能出现的各种情况,选择效果最优的
我们可以对物体进行拆分,从而降低时间复杂度。记第i种物品有n[i]件,则拆分的系数为:
1,2,4,...,2k−1,n[i]−2k+1
1
,
2
,
4
,
.
.
.
,
2
k
−
1
,
n
[
i
]
−
2
k
+
1
,将物体的费用和价值分别乘以这些系数,得到拆分后等价的物体,物体就被拆分为
log(n[i])+1
l
o
g
(
n
[
i
]
)
+
1
件物品。
代码:GitHub
4. 混合背包
在一个背包问题中,一些物体只有一件(01背包)、一些物体有无穷多件(完全背包)、一些物体有有限数目件(多重背包)。对于容量给定的背包,如何选择物体,使得可以带走最多的价值?
求解混合背包问题,先写出01背包、完全背包和多重背包这三种问题对应的程序。在迭代中,对于具体的物体按照具体的程序进行迭代即可。
5. 二维费用的背包问题
当费用增加一维时,状态也增加一维,状态转化方程为:
f[i][u][v]=max{f[i−1][u][v],f[i−1][u−c[i]][v−d[i]]+w[i]}
f
[
i
]
[
u
]
[
v
]
=
m
a
x
{
f
[
i
−
1
]
[
u
]
[
v
]
,
f
[
i
−
1
]
[
u
−
c
[
i
]
]
[
v
−
d
[
i
]
]
+
w
[
i
]
}
6. 分组背包问题
每一个分组的选择策略包括:选择本组中的某一件物品或者一件物品也不选,按组进行迭代
for i所有组k:
for f = bag size <– 0
for 组k中的所有物体
f[v] = max{f[v], f[v-c[i]]+w[i]}
7. 有依赖的背包问题
首先,考虑在一个主件和它的附件集合中进行选择,若含有n个附件,则选择的策略有 2n+1 2 n + 1 种,范围巨大。由于各个策略之间互斥,考虑对于费用相同的策略,只保留价值最高的一个。采用“01背包”方法处理附件集合,得到长度为v-c[i]+1的物品组及其对应的最大价值。在此基础上再采用分组背包问题的方法求解。