背包九讲
目录
- 01背包问题
- 完全背包问题
- 多重背包问题
- 混合三种背包问题
- 二维费用的背包问题
- 分组的背包问题
- 有依赖的背包问题
- 泛化物品
- 背包问题问法的变化
01背包问题
基本问题
有 \(N\) 件物品和一个容量为 \(V\) 的背包。第 \(i\) 件物品的费用是 \(c[i]\),价值是 \(w[i]\)。求解将哪些物品装入背包可使价值总和最大。
基本思路
\(f[i][v]\) 表示前 \(i\) 件物品恰放入一个容量为 \(v\) 的背包可以获得的最大价值。则其状态转移方程便是:
\(f[N][V]\) 则为背包可能的最大价值。
优化空间复杂度
时间复杂度为\(O(N\times V)\),空间复杂度可以优化为\(O(V)\)。
for i = 1..N
for v = V..0
f[v] = max{f[v], f[v - c[i]] + w[i]};
初始化的细节问题
- 要求恰好装满背包:\(f[0]\) 设置为0,其他\(f[1..V]\) 均设置为 \(- \infty\)。
- 不要求恰好装满背包,只要求价值最大:\(f[0..V]\)均设置为0。
题目
完全背包问题
基本问题
有 \(N\) 种物品和一个容量为 \(V\) 的背包,每种物品都有无限件可用。第 \(i\) 种物品的费用是 \(c[i]\),价值是\(w[i]\)。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
基本思路
令 \(f[i][v]\) 表示前 \(i\) 种物品恰放入一个容量为 \(v\) 的背包的最大权值。可以得到状态转移方程式:
改进做法
我们可以通过一个 \(O(N\times V)\) 的方法进行完全背包问题求解。状态转移方程式为:
\(f[V]\) 则为背包可能的最大价值。
for i = 1..N
for v = 0..V
f[v] = max{f[v], f[v - cost] + weight}
题目
多重背包问题
基本问题
有 \(N\) 种物品和一个容量为 \(V\) 的背包。第 \(i\) 种物品最多有 \(n[i]\)件可用,每件费用是 \(c[i]\),价值是 \(w[i]\)。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
基本思路
令 \(f[i][v]\) 表示前 \(i\) 种物品恰放入一个容量为 \(v\) 的背包的最大权值,则有状态转移方程:
复杂度为 \(O(V \times \sum_i n[i])\)。
二进制优化
将第 \(i\) 种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为 \(1,2,4,...,2^{k-1}, n[i] - 2^{k+1}\),且 \(k\) 是满足 \(n[i] - 2^{k+1} > 0\) 的最大整数。例如:如果 \(n[i]\) 为 \(13\),就将这种物品分成系数分别为 \(1,2,4,6\) 的四件物品。
这样就将第 \(i\) 种物品分成了 \(O(\log n[i])\) 种物品,将原问题转化为了复杂度为 \(O(V\times \sum \log n[i])\) 的 01 背包问题 。
procedure ZeroOnePack(cost, weight)
for v = V..cost
f[v] = max{f[v], f[v - cost] + weight}
procedure CompletePack(cost, weight)
for v = cost..V
f[v] = max{f[v], f[v - cost] + weight}
procedure MultiplePack(cost, weight, amount)
if cost * amount >= V {
CompletePack(cost, weight)
Return
}
integer k = 1
while k < amount {
ZeroOnePack(k * cost, k * weight)
amount = amount - k
k = k * 2
}
ZeroOnePack(amount * cost, amount * weight)
单调队列优化
基于基本算法的状态转移方程,应用单调队列的方法使每个状态的值可以以均摊 \(O(1)\) 的时间求解。总的复杂度可以达到 \(O(NV)\)
题目
混合三种背包问题
基本问题
有的物品只可以取一次(01 背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。即将前三种情况混合起来。求解背包可能的最大价值。
基本思路
混合使用以上的三种背包问题求解方法。
for i = 1..N
if 第 i 件物品是 01 背包
ZeroOnePack(c[i], w[i])
else if 第 i 件物品是完全背包
CompletePack(c[i], w[i])
else if 第 i 件物品是多重背包
MultiplePack(c[i], w[i], n[i])
二维费用的背包问题
基本问题
对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为 代价 1 和 代价 2 ,第 \(i\) 件物品所需的两种代价分别为 \(a[i]\) 和 \(b[i]\)。两种代价可付出的最大值(两种背包容量)分别为 \(V\) 和 \(U\)。物品的价值为 \(w[i]\)。
基本思路
设 \(f[i][v][u]\) 表示前 \(i\) 件物品付出两种代价分别为 \(v\) 和 \(u\) 时可获得的最大价值。状态转移方程为:
物品个数限制
有时,“二维费用”的条件是以这样一种隐含的方式给出的:最多只能取 \(M\) 件物品。这事实上相当于每件物品多了一种“件数”的费用,每个物品的件数费用均为 \(1\),可以付出的最大件数费用为 \(M\)。
设 \(f[v][m]\) 表示付出费用 \(v\)、最多选 \(m\) 件时可得到的最大价值,则根据物品的类型(01、完全、多重)用不同的方法循环更新,最后在 \(f[0..V][0..M]\) 范围内寻找答案
分组的背包问题
基本问题
有 \(N\) 件物品和一个容量为 \(V\) 的背包。第 \(i\) 件物品的费用是 \(c[i]\),价值是 \(w[i]\)。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
基本思路
\(f[k][v]\) 表示前 \(k\) 组物品花费费用 \(v\) 能取得的最大权值,则有:
伪代码如下。注意三层 for 循环的顺序不能写错。
for 所有的组 k
for v = V..0
for 所有的 i 属于组 k
f[v] = max{f[v], f[v - c[i]] + w[i]}
有依赖的背包问题
基本问题
这种背包问题的物品间存在某种 依赖 的关系。也就是说,\(i\) 依赖于 \(j\),表示
若选物品 \(i\),则必须选物品 \(j\)。为了简化起见,我们先设没有某个物品既依赖于
别的物品,又被别的物品所依赖;另外,没有某件物品同时依赖多件物品。
基本思路
我们可以对主件 \(i\) 的“附件集合”先进行一次 01 背包,得到费用依次为 \(0..V-c[i]\)所有这些值时相应的最大价值 \(f'[0..V-c[i]]\)。那么这个主件及它的附件集合相当于 \(V-c[i]+1\) 个物品的物品组,其中费用为 \(c[i]+k\) 的物品的价值为 \(f'[k]+w[i]\)。也就是说原来指数级的策略中有很多策略都是冗余的,通过一次 01 背包后,将主件 \(i\) 转化为 \(V-c[i]+1\) 个物品的物品组,就可以直接应用分组背包问题 =的算法解决问题了。
泛化物品
泛化物品的定义
考虑这样一种物品,它并没有固定的费用和价值,而是它的价值随着你分配给它
的费用而变化。这就是泛化物品的概念。
更严格的定义是:在背包容量为 \(V\) 的背包问题中,泛化物品是一个定义域为 \(0..V\)
中的整数的函数 \(h\),当分配给它的费用为 \(v\) 时,能得到的价值就是 \(h(v)\)。