问题描述:有n个物品,第 i 个物品的重量与价值分别为 w [ i ] w[i] w[i] 与 v [ i ] v[i] v[i]且第 i 种物品最多有 p[i] 件。背包容量为 V,试问在每个物品不超过其上限的件数(物品必须保持完整)的情况下,如何让背包装入的物品具有更大的价值总和。现有数据如下:
w = [2,3,4,5];
v = [3,4,5,6];
p = [1,1,1,1]; // 结果与0-1背包一样。
V = 8;
解题思路:令 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示从编号 1~i 的物品中挑选任意数量的任意物品放入容量为 j 的背包中得到的最大价值,那么有 d p [ i ] [ j ] = m a x { d p [ i − 1 ] [ j − k ∗ w [ i ] ] + k ∗ v [ i ] ∣ 0 ≤ k ≤ p [ i ] & k ∗ w [ i ] ≤ j } dp[i][j]=max\{dp[i-1][j-k*w[i]]+k*v[i]|0 \le k \le p[i] \& k*w[i] \le j\} dp[i][j]=max{dp[i−1][j−k∗w[i]]+k∗v[i]∣0≤k≤p[i]&k∗w[i]≤j}。
public int knapsackProblem(int[] w, int[] v, int[] p, int cap) {
int[][] dp = new int[w.length + 1][cap + 1];
for (int i = 1; i <= w.length; i++) {
for (int j = 1; j <= cap; j++) {
for (int k = 0; k <= p[i - 1] && k * w[i - 1] <= j; k++) {
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * w[i - 1]] + k * v[i - 1]);
}
}
}
return dp[w.length][cap];
}
时间复杂度为 O ( n V ∑ p [ i ] ) O(nV \sum p[i]) O(nV∑p[i]),略高。
时间优化:将该问题转换为0-1背包问题,即若第 i 种物品有 s 件,那么可以将其分成 s 份”不同“的物品。但是略复杂。
二进制优化法:若要表示1~s之内的任意一个数字,只需要 l o g 2 ( s ) log_2(s) log2(s) 向上取整个数即可完全表示,分别取 1 , 2 , 4 , ⋯ , 2 l o g 2 ( x ) 1,2,4,\cdots, 2^{log_{2}(x)} 1,2,4,⋯,2log2(x) 个数,若最有一个数不足 2 l o g 2 ( x ) 2^{log_{2}(x)} 2log2(x),则取 s − 1 − 2 − 4 − ⋯ s-1-2-4-\cdots s−1−2−4−⋯。即十进制数10可以用4个数表示,分别是1,2,4,3。前三个数最大能表示的数是7。故剩下一个数取10-7=3。此时原问题就转换为0-1背包问题了。
public int knapsackProblem(int[] w, int[] v, int[] p, int cap) {
int[] dp = new int[cap + 1];
for (int i = 0; i < w.length; i++) {
int s = p[i];
for (int j = 1; j <= s; s -= j, j <<= 1) {
for (int k = cap; k >= 0 && k >= j * w[i]; k--) {
dp[k] = Math.max(dp[k], dp[k - j * w[i]] + j * v[i]);
}
}
if (s > 0) {
for (int j = cap; j >= s * w[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - s * w[i]] + s * v[i]);
}
}
}
return dp[cap];
}
时间复杂度为 O ( n V ∑ l o g 2 ( p [ i ] ) ) O(nV\sum log_{2}(p[i])) O(nV∑log2(p[i]))。
上一篇:背包问题之完全背包问题
下一篇:背包问题之混合背包问题