目录
完全背包
有一个大小为 m 的背包,有 N 种物体,每种物品的价值为 Vi, 大小为 Ai, 并且每种物品有无限个,请问背包能容纳的最大价值是多大?
(Lintcode - 440)
这就是完全背包问题,完全背包是指物品的数量都是无限个。
显然,我们可以将其转换为 01 背包。每种物体虽然是无限个,但不可能超过背包的容量,假设第一种物品的大小是 A1, 我们可以假装有一系列物体,大小分别为 A1,2A1,3A1…一直到 (m / A1) A1 。然后将 kA1 分别放入背包。这样的时间复杂度为 O(VN*Sum(k)),非常之大。其状态转移方程为:
dp[i + 1][j] = max(dp[i][j], dp[i][j - k * A[i]] + k * V[i])
for (int i = 0; i < A.size(); ++i)
for (int j = m; j >= 0; --j)
for (int k = 0; k * A[i] <= m; ++k)
if (j - k * A[i] >= 0)
f[j] = max(f[j], f[j - k * A[i]] + k * V[i]);
优化一:输入优化
给我们的物品列表如果较多时,我们可以先改善下物品列表。对于物品 j, k,若 Aj >= Ak 并且 Vj < Vk,那么 j 相对于 k 又贵又没价值,肯定是可以抛弃的。
另外,物品列表中可能有大小大于 m 的物品,需要排除掉。
优化二:二进制
假如最多只能放入 19 个Ai,其实我们只需要分别往包里放 1,2,4,8,4 个Ai就行了。物体都可以拆成二进制的个数往包里放(其实拆成其他进制也可以),最终组合出的数目也是 19 个,等同于往包里放了 19 个Ai。时间复杂度可以降为O(VN*Sum(log(k)))。
for (int i = 0; i < A.size(); ++i) {
int j = 1;
while(j * A[i] < m) {
for (int k = m; k >= j * A[i]; --k)
f[k] = max(f[k], f[k - j * A[i]] + j * V[i]j;
j = j << 1;
}
}
优化三:重复放入的 01 背包
可能有些同学会记得,我们在写 01 背包时出了个错:从前到后遍历背包容量,导致同一种物体被重复放入。
而这恰恰是我们在完全背包里想要的,物品数量无限,当然可以一直往背包里放。
时间复杂度为 O(VN)。
for (int i = 0; i < A.size(); ++i)
for (int j = A[i]; j <= m; ++j)
f[j] = max(f[j], f[j - A[i]] + V[i]);
多重背包
有一个大小为 m 的背包,有 N 种物体,每种物品的价值为 Vi, 大小为 Ai, 该种物体总共有 Ci 个,请问背包能容纳的最大价值是多大?(Lintcode-798)
我们会很容易想到将每个物体分开放入的方法,将这道题化作 01 背包来放入,分别将每个物体放入 Ci 次。时间复杂度为 O(VNC)。
for (int i = 0; i < prices.size(); ++i)
for (int k = 1; k <= amounts[i]; ++k)
for (int j = n; j >= prices[i]; --j)
dp[j] = max(dp[j - prices[i]] + weight[i], dp[j]);
这种方法也可以采用二进制优化,将时间复杂度降到 O(VNlog(C))。
多重背包也可以用单调队列优化到 O(VN) 的方法,但我目前只学习了单调队列,还没看懂如何优化多重背包问题。之后看懂了再补上。
总结
目前已经知道基础的背包问题如何解,进一步的背包问题一般是求多少种方法能填满背包,或者是混合三种背包。这样的简单变化都可以运用目前讲述过的方法来进行解答。