完全背包
完全背包是指,在N个物品中,每个物品都有无限个。其中每个物品的体积为 vi[i], 每个物品的权重(价值)为 wi[i]。
对于每件物品,跟01背包相同,只有选和不选两种情况。区别在于,该物品可以选择一个或多个。
在一个体积为V的背包中,怎么选择放入物品,可使得价值最大。
注:本帖子中的所有内容,均以java代码进行演示
问题一:
点击题目,跳转到原题。
剖析;
完全背包,跟 01背包,大抵相同。所以,直接分析和 01背包 的不同。
在01背包中,一个物品,只有选和不选,两种情况。而对于完全背包,则在此基础上,多了对于一个物品,若选择该物品,则应该选择多少件。注意到本题中的,数据范围很小,则可以考虑,直接枚举该物品选择多少件。
dp[i] [j] 表示,只是用前 i 件物品(包括第 i 件物品),在体积为 j 的情况下,能够获得的最大价值。
情况一:若不选择第 i 件物品,则获得的价值为 dp【i-1】【j】
情况二:若选择第 i 件物品,并且该物品选择 k 件,所获得的价值为 dp【i-1】【j -v [i] * k】+ k * w[i],k 可取0-正无穷,如果条件满足的情况下。因为,在完全背包中,每件物品的个数为正无穷。当 k 为0时,即为情况一。
抽取输入输出,打印以及main函数如下:
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
//scanner.next() 标准输出函数
//物品数量
int n = scanner.nextInt();
//最大体积
int v = scanner.nextInt();
//第i件物品所占的体积
int[] vi = new int[n + 1];
//第i件物品的权重
int[] wi = new int[n + 1];
// 接下来有 N 行,每行有两个整数:v[i],w[i],用空格隔开,分别表示第i件物品的体积和价值
for (int i = 1; i <= n; i++) {
vi[i] = scanner.nextInt();
wi[i] = scanner.nextInt();
}
allBackpacker(n, v, vi, wi);
}
}
完全背包,直接枚举 第 i 件物品,所选择的个数,代码实现如下
//二维数组,三重循环
private static void allBackpacker(int n, int v, int[] vi, int[] wi) {
int[][] dp = new int[n + 1][v + 1];
//只是用前i件物品,包括第i件
for (int i = 1; i <= n; i++) {
//体积为j
for (int j = 0; j <= v; j++) {
//选择第 i 件物品的数量为 k
for (int k = 0; k * vi[i] <= j; k++)
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * vi[i]] + k * wi[i]);
}
}
System.out.println(dp[n][v]);
}
观察 dp[i] [j] 可得
故,上述代码可以优化为:
//二维数组,两重循环
private static void allBackpacker(int n, int v, int[] vi, int[] wi) {
int[][] dp = new int[n + 1][v + 1];
//只是用前i件物品,包括第i件
for (int i = 1; i <= n; i++) {
//体积为j
for (int j = 0; j <= v; j++) {
//不使用这个物品
dp[i][j] = dp[i - 1][j];
//使用这个物品
if (j >= vi[i])
//由于 j 是递增的,所以j - vi[i] 会在j 之前被计算
dp[i][j] = Math.max(dp[i][j], dp[i][j - vi[i]] + wi[i]);
}
}
System.out.println(dp[n][v]);
}
根据 01背包 ,可知,能够利用滚动数组,把二维数组优化为一维的。
由于在计算 dp[i] [j] ,的过程中,只用到了 第 i 个物品,并没有用到第 i-1 个物品,故可以将 dp 直接优化为一维数组。
优化如下:
//一维数组
private static void allBackpacker(int n, int v, int[] vi, int[] wi) {
int[] dp = new int[v + 1];
for (int i = 1; i <= n; i++) {
//j小于vi[i]的部分,会在其他物品循环的时候,被计算到
for (int j = vi[i]; j <= v; j++) {
//dp方程都跟第i层有关,所以,可以直接转化为一维
//dp[i][j] = Math.max(dp[i][j], dp[i][j - vi[i]] + wi[i]);
dp[j] = Math.max(dp[j], dp[j - vi[i]] + wi[i]);
}
}
System.out.println(dp[v]);
}
这里,我们可以推出一个一般性的结论,如果dp在计算过程中, 体积 j 用到了上一层(即 i-1 层)中比 j 小的dp 值,那么 j 应该 从大到小,进行遍历。因为,如果 j 从小到大,进行遍历的话,在一维数组中,会将上一层dp 的值覆盖掉。否则,则应该从小到大进行遍历。
感谢您的阅读!