完全背包问题
之前已经讲解过0-1背包问题,今天来讨论另外一种:完全背包问题
1. 问题描述
之前0-1背包问题的问题描述如下
给出n个物品的体积A[i]和其价值V[i],将他们装入一个大小为m的背包,最多能装入的总价值有多大?
而今天要讨论的完全背包问题描述如下
给出n种物品的体积A[i]和其价值V[i],每一种物品都有无限个,将这n种物品装入一个大小为m的背包,最多能装入的总价值有多大?
我们可以发现区别在于:对于每一种物体,不再是之前0-1
问题,而是0-k
,k
表示能放多个,k有可能是0,1,2…
2. 转移方程
我们构造一个二维的转移数组dp[i][j]
,表示前面i
个物品放入总体积为j
的背包中所获得的最大价值,那么:
1 | dp[i][j] = max(dp[i-1][j], dp[i-1][j-k*A[i]] + k*V[i]); k = 0,1,...k_max, where k*A[i] <= j |
二维的代码非常容易写,如下:
1 | int backPackII(int m, vector<int> A, vector<int> V) { |
3. 优化
3.1 二维变一维
从转移方程可以看到,实际当前i
的所有状态都只依赖于i-1
,所以上面的二维数组很容易优化到一维(像0-1背包问题中可以先把二维转变成两个一维数组,在此略过),直接给出用一个一维数组的代码:
1 | int backPackII(int m, vector<int> A, vector<int> V) { |
这里我们的for (int j = A[i]; j <= m; j++)
是正着写的,如果和0-1背包问题一样for (int j = m; j >= A[i]; j--)
反着写也没有问题。
3.2 再优化
上面的代码有很多乘法和if判断,导致程序很慢,同样的测试数据上面的代码时间是1200ms左右,而下面的就260ms左右:
1 | int backPackII(int m, vector<int> A, vector<int> V) { |
这里我们的for (int j = A[i]; j <= m; j++)
是正着写的,如果是for (int j = m; j >= A[i]; j--)
就会有问题。
因为我们现在是一件物品可以选0-k件,而不是0-1件,大一点的j要依赖小一点的j的dp最大值,比如小一点的j可能在k=1是取到最大值,然后大一点的j需要k=2才最大,如果反过来写for循环,那么k就只能是0或1,与题设不符合,这里需要读者细细体会!!!