背包详解:完全背包与多重背包

目录

完全背包

有一个大小为 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) 的方法,但我目前只学习了单调队列,还没看懂如何优化多重背包问题。之后看懂了再补上。

总结

  目前已经知道基础的背包问题如何解,进一步的背包问题一般是求多少种方法能填满背包,或者是混合三种背包。这样的简单变化都可以运用目前讲述过的方法来进行解答。

返回目录

猜你喜欢

转载自blog.csdn.net/weixin_43072157/article/details/82217141