完全背包问题(Java)
完全背包问题
需要先了解 01背包问题详解
题目
有 NN 种物品和一个容量是V
的背包,每种物品都有无限件可用。
第 ii 种物品的体积是 vi
,价值是 wi
。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N
,V
,用空格隔开,分别表示物品种数和背包容积。
接下来有 NN 行,每行两个整数 vi
,wi
,用空格隔开,分别表示第 i
种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤10000<N,V≤1000
0<vi,wi≤10000<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10
题解
在**01背包问题**中,我们已经讨论了 输入
(即数组是从第一位,还是第二位)对状态转移方程的影响,在完全背包
问题中,就不再赘述;本文重点讨论如何一步一步优化代码
朴素解法
思路:
1.从前i个物品中选择,选择物品的总重量不超过j,的情况下获得的物品最大值
2.i表示物品的选择范围,所以有:i的范围是[1—n]
3.j表示枚举每一个重量上限,所以有:j的范围是[1—bagVolume]
4…k表示枚举每一个物品的选择个数,所以有:K个物品的范围是[0—k*volume[i]]
5…考虑第一个情况,dp[1,0]表示选择一个物品,背包的最大重量为0时的最大价值,考虑其意义,值为0,所以dp数组无需初始化
代码:
import java.util.Scanner;
/**
* @see 完全背包问题 https://www.acwing.com/problem/content/3/
*/
public class completeBackpackIOClass {
static int N = 1010;
static int n, bagVolume;
static int[] volume = new int[N];
static int[] worth = new int[N];
static int[][] dp = new int[N][N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
bagVolume = sc.nextInt();
//一定要注意,输入输出版的背包问题,2个数组都是从第二个数开始填入的
for (int i = 1; i <= n; i++) {
volume[i] = sc.nextInt();
worth[i] = sc.nextInt();
}
for(int i = 1; i <= n; i++)
for(int j = 0; j <= bagVolume; j++)
for (int k = 0;k*volume[i]<=j;k++)
dp[i][j] = Math.max(dp[i][j],dp[i-1][j-k*volume[i]]+worth[i]);
System.out.println(dp[n][bagVolume]);
}
}
时间优化
时间优化原理:
把枚举数量的一层循环去掉,等价于:dp[i][j]= max(dp[i - 1][j], dp[i - 1][j - v] + w, dp[i - 1][j - 2 * v] + 2 * w, dp[i - 1][j - 3 * v] + 3 * w);
而列举后发现:
dp[i][j - v] = max(dp[i - 1][j - v], dp[i - 1][j - 2 * v] + w, dp[i - 1][j - 3 * v] + 2 * w, .....);
发现:
dp[i][j - v]+ w
与 dp[i][j]= max(dp[i - 1][j],...)
中的...
完全相等,因此用dp[i][j - v]+w
代换...
后得 :
dp[i][j] = max(dp[i - 1][j], dp[i][j - v] +w);
package ACwing3完全背包问题;
/**
* @see 完全背包问题 https://www.acwing.com/problem/content/3/
*/
import java.util.Scanner;
public class completeBagTimeOptimizationClass {
static int N = 1010;
static int n, bagVolume;
static int[] volume = new int[N];
static int[] worth = new int[N];
static int[][] dp = new int[N][N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
bagVolume = sc.nextInt();
//一定要注意,输入输出版的背包问题,2个数组都是从第二个数开始填入的
for (int i = 1; i <= n; i++) {
volume[i] = sc.nextInt();
worth[i] = sc.nextInt();
}
/**
* 时间优化的原理:
* 把枚举数量的一层循环去掉,等价于:
* dp[i][j]= max(dp[i - 1][j], dp[i - 1][j - v] + w, dp[i - 1][j - 2 * v] + 2 * w, dp[i - 1][j - 3 * v] + 3 * w);
* 而,列举后发现:
* dp[i][j - v] = max(dp[i - 1][j - v], dp[i - 1][j - 2 * v] + w, dp[i - 1][j - 3 * v] + 2 * w, .....);
* 发现dp[i][j - v]+ w 与 dp[i][j]= max(dp[i - 1][j],...) 中的...完全相等,因此用dp[i][j - v]+w代换...后得 :
* dp[i][j] = max(dp[i - 1][j], dp[i][j - v] +w);
*/
for(int i = 1; i < n; i++) {
for(int j = 0; j < bagVolume; j++) {
dp[i][j] = dp[i-1][j];
if (j>=volume[i]){
dp[i][j] = Math.max(dp[i][j],dp[i][j-volume[i]]+worth[i]);
}
}
}
System.out.println(dp[n][bagVolume]);
}
}
时间,空间双优化
空间优化:
按照对01背包问题优化的思路,在时间优化版本的基础上,进行空间优化
仔细观察 完全背包问题的最终优化版,和01背包问题非常相似
唯一的不同就是内层循环的遍历顺序
实际上01背包从大到小就是使用上一行的状态更新。完全背包是使用当前行的状态更新,因为当前物品有无限个
如果01背包不倒着来,就相当于本来只能用1次的物品可以多次使用了
package ACwing3完全背包问题;
import java.util.Scanner;
public class completeBagDoubleOptimizationClass {
static int N = 1010;
static int n, bagVolume;
static int[] volume = new int[N];
static int[] worth = new int[N];
static int[] dp = new int[N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
bagVolume = sc.nextInt();
//一定要注意,输入输出版的背包问题,2个数组都是从第二个数开始填入的
for (int i = 1; i <= n; i++) {
volume[i] = sc.nextInt();
worth[i] = sc.nextInt();
}
/**
* 空间优化:
* 按照对01背包问题优化的思路,在时间优化版本的基础上,进行空间优化
* 仔细观察 完全背包问题的最终优化版,和01背包问题非常相似
* 唯一的不同就是内层循环的遍历顺序
* 实际上01背包从大到小就是使用上一行的状态更新。完全背包是使用当前行的状态更新,因为当前物品有无限个
* 如果01背包不倒着来,就相当于本来只能用1次的物品可以多次使用了
*/
for (int i = 1; i < n; i++) {
for (int j = volume[i]; j < bagVolume; j++) {
dp[j] = Math.max(dp[j], dp[j - volume[i]] + worth[i]);
}
}
System.out.println(dp[bagVolume]);
}
}
终极优化版(在输入时同步处理)
import java.util.Scanner;
public class completeBagDoubleOptimizationPlusClass {
static int N = 1010;
static int[] dp = new int[N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n =sc.nextInt(),bagVolume = sc.nextInt();
//一定要注意,输入输出版的背包问题,2个数组都是从第二个数开始填入的
for(int i = 1; i <= n; i++) {
int volume = sc.nextInt(),worth = sc.nextInt();
for (int j = volume; j <=bagVolume ; j++)
dp[j] = Math.max(dp[j], dp[j-volume]+worth);
}
System.out.println(dp[bagVolume]);
}
}
刷题不迷路,欢迎关注博主的git题集,这里有最详细的分类,最经典的例题和最全面的注释
感兴趣的同学可以赏个star吗:算法刷题集git地址 题目源码地址