一、01背包问题
Q:
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
A:
package acwing.packageQ; import java.util.Scanner; /** * @author Millet * @date 2020/5/26 9:45 */ public class PackageQ1 { public static void main(String[] args) { Scanner in = new Scanner(System.in); int n = in.nextInt(); int v = in.nextInt(); int[] weight = new int[n+1]; int[] value = new int[n+1]; for(int i=1;i<=n;i++){ weight[i] = in.nextInt(); value[i] = in.nextInt(); } int res = maxValue(value, weight,n,v); int res2 = optimize(value,weight,n,v); System.out.println(res); System.out.println(res2);//优化 } /** * 动态规划: * 定义一个二维矩阵dp[i][j]表示前i个物品,背包容量j情况下的最优解 * 在判断第i个物品时有两种情况: * 1.第i件物品加进来比背包容量大,则不选 dp[i][j] = dp[i-1][j] * 2.第i件物品加进来比背包容量小,则通过判断价值是否选 * 选:前i-1件物品放入容量为j-w[i]的背包中最大价值 * 不选:前i-1件物品放入容量为j的背包中最大价值 * dp[i][j] = MAX( dp[i-1][j], dp[i-][j-weight[i]]+value[i] ) * @param value * @param weight * @param n * @param v * @return */ private static int maxValue(int[] value,int[] weight, int n, int v) { int[][] dp = new int[n+1][v+1]; for(int i=1;i<=n;i++){//dp[0][0-v]都为0 for(int j=0;j<=v;j++){//背包容量 if(j < weight[i]){//当前物品重量大于背包容量 dp[i][j] = dp[i-1][j]; }else { dp[i][j] = Math.max( dp[i-1][j], dp[i-1][j-weight[i]]+value[i] ); } } } return dp[n][v];//前n个物品,背包容量为v情况下最大价值 } /** * 动态规划:一维数组 * dp[j] = Max( dp[j], dp[j-weight[i]] + value[i] ) * @param value * @param weight * @param n * @param v * @return */ private static int optimize(int[] value, int[] weight, int n, int v) { int[] dp = new int[v+1];//表示当前背包重量为j的最大价值 for(int i=1;i<=n;i++){ //注意:这里是倒序进行遍历,是为了解决覆盖旧值问题,可以和完全背包问题进行比较 /* 动态规划是通过上一轮状态推导出当前一轮的状态((保证物品只使用一次或零次)),如果正序推导dp数组 ,那么是从dp[0-v],后面的数组更新则使用了新的一轮dp值,例如dp[8]使用了 dp[3],而dp[3]如果按照正序遍历的话,已经是新的一轮值了。 */ /* 首先dp数组初始化全为0:给定物品种类有4种,包最大体积为5,数据来源于题目的输入 weight value 1 2 2 4 3 4 4 5 dp[j] = Math.max( dp[j], dp[j-weight[i]] + value[i]) [0,0,0,0,0,0] i = 1时:j从5到weight[1] dp[5] = max(dp[5],dp[4]+value[1]) = value[1] = 2 dp[4] = max(dp[4],dp[3]+value[1]) = value[1] = 2 dp[3] = max(dp[3],dp[2]+value[1]) = value[1] = 2 dp[2] = max(dp[2],dp[1]+value[1]) = value[1] = 2 dp[1] = max(dp[1],dp[0]+value[1]) = value[1] = 2 [0,2,2,2,2,2] i = 2时:j从5到weight[2] dp[5] = max(dp[5],dp[3]+value[2]) = dp[3]+value[2] = 6 dp[4] = max(dp[4],dp[2]+value[2]) = dp[2]+value[2] = 6 dp[3] = max(dp[3],dp[1]+value[2]) = dp[1]+value[2] = 6 dp[2] = max(dp[2],dp[0]+value[2]) = dp[0]+value[2] = 4 [0,2,4,6,6,6] i = 3 时:j从5到weight[3] dp[5] = max(dp[5],dp[2]+value[3]) = dp[2]+value[3] = 8 dp[4] = max(dp[4],dp[1]+value[3]) = dp[1]+value[3] = 6 dp[3] = max(dp[3],dp[0]+value[3]) = dp[3] = 6 [0,2,4,6,6,8] i = 4 时: j从5到weight[4] dp[5] = max(dp[5],dp[1]+value[4]) = dp[5] = 8 dp[4] = max(dp[4],dp[0]+value[4]) = dp[4] = 6 [0,2,4,6,6,8] */ for(int j=v;j>=weight[i];j--){ dp[j] = Math.max( dp[j], dp[j-weight[i]] + value[i]); } } return dp[v]; } }
二、完全背包问题
Q:
题目如上题,只不过条件变成“ 每种物品都有无限件可用 ”
A:
package acwing.packageQ; import java.util.Scanner; /** * @author Millet * @date 2020/5/26 11:01 */ public class PackageQ2 { public static void main(String[] args) { Scanner in = new Scanner(System.in); int n = in.nextInt(); int v = in.nextInt(); int[] weight = new int[n + 1]; int[] value = new int[n + 1]; for (int i = 1; i <= n; i++) { weight[i] = in.nextInt(); value[i] = in.nextInt(); } System.out.println(optimize(value,weight,n,v)); } /** * 动态规划:一维数组 * @param value * @param weight * @param n * @param v * @return */ private static int optimize(int[] value, int[] weight, int n, int v) { int[] dp = new int[v+1]; for(int i=1;i<=n;i++){ //注意:此处正序遍历,和01背包问题不同,本题物品可重复使用,因此不在乎是否使用新值 /* 首先dp数组初始化全为0:给定物品种类有4种,包最大体积为5,数据来源于题目的输入 weight value 1 2 2 4 3 4 4 5 i = 1 时: j从weight[1]到5 dp[1] = max(dp[1],dp[0]+value[1]) = value[1] = 2 (用了一件物品1) dp[2] = max(dp[2],dp[1]+value[1]) = value[1] + value[1] = 4(用了两件物品1) dp[3] = max(dp[3],dp[2]+value[1]) = value[1] + value[1] + value[1] = 6(用了三件物品1) dp[4] = max(dp[4],dp[3]+value[1]) = value[1] + value[1] + value[1] + value[1] = 8(用了四件物品1) dp[5] = max(dp[3],dp[2]+value[1]) = value[1] + value[1] + value[1] + value[1] + value[1] = 10(用了五件物品) i = 2 时:j从weight[2]到5 dp[2] = max(dp[2],dp[0]+value[2]) = value[1] + value[1] = value[2] = 4(用了两件物品1或者一件物品2) dp[3] = max(dp[3],dp[1]+value[2]) = 3 * value[1] = value[1] + value[2] = 6(用了三件物品1,或者一件物品1和一件物品2) dp[4] = max(dp[4],dp[2]+value[2]) = 4 * value[1] = dp[2] + value[2] = 8(用了四件物品1或者,两件物品1和一件物品2或两件物品2) dp[5] = max(dp[5],dp[3]+value[2]) = 5 * value[1] = dp[3] + value[2] = 10(用了五件物品1或者,三件物品1和一件物品2或一件物品1和两件物品2) i = 3时:j从weight[3]到5 dp[3] = max(dp[3],dp[0]+value[3]) = dp[3] = 6 # 保持第二轮的状态 dp[4] = max(dp[4],dp[1]+value[3]) = dp[4] = 8 # 保持第二轮的状态 dp[5] = max(dp[5],dp[2]+value[3]) = dp[4] = 10 # 保持第二轮的状态 i = 4时:j从weight[4]到5 dp[4] = max(dp[4],dp[0]+value[4]) = dp[4] = 10 # 保持第三轮的状态 dp[5] = max(dp[5],dp[1]+value[4]) = dp[5] = 10 # 保持第三轮的状态 */ for(int j=weight[i];j<=v;j++){ dp[j] = Math.max( dp[j], dp[j-weight[i]] + value[i]); } } return dp[v]; } }
三、多重背包问题Ⅰ
Q:
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<vi,wi,si≤100
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
A1:
可以理解成01背包问题的变种,将多个相同的物品看作个体。
package acwing.packageQ; import java.util.Scanner; /** * @author Millet * @date 2020/5/26 14:57 */ public class PackageQ3 { public static void main(String[] args) { Scanner in = new Scanner(System.in); int n = in.nextInt(); int v = in.nextInt(); int[] weight = new int[n + 1]; int[] value = new int[n + 1]; int[] num = new int[n + 1]; for (int i = 1; i <= n; i++) { weight[i] = in.nextInt(); value[i] = in.nextInt(); num[i] = in.nextInt(); } System.out.println(maxValue(value,weight,num,n,v)); } private static int maxValue(int[] value, int[] weight, int[] num, int n, int v) { int[] dp = new int[v+1]; for(int i=1;i<=n;i++){//dp[0][0-v]都为0 for(int j=v;j>=weight[i];j--){ for(int k=1;k<=num[i];k++){ if(j >= k*weight[i]) dp[j] = Math.max( dp[j], dp[j-k*weight[i]] + k*value[i]); } } } return dp[v]; } }
A2:二进制优化:
package acwing.packageQ; import java.util.Scanner; /** * @author Millet * @date 2020/5/26 19:43 */ public class PackageQ4 { public static void main(String[] args) { Scanner in = new Scanner(System.in); int n = in.nextInt(); int v = in.nextInt(); /* 使用二进制拆分 * 1. 普通方法是将相同的多个物品拆看成单个特例,进行逐个遍历比较, * 但是每次都要遍历num[i]次,复杂。例如11个相同物品,需要遍历11次。 * 2.使用二进制拆分成几个数来表示1~num[i],例如1 2 4 4,可以表示1~11 * Q:那么是如何获得这几个数呢? * 原数不断减去2的整次幂,直到最后比剩余数大,无法再减,则再加上最后一个 * 11-1=10,10-2=8,8-4=4,4>=4直接添加 */ //构造新的value,weight //0<V<2000,数组最大容量每个si能拆出来的个数 + 左右预留的哨兵位置 = 2000 * log2(2000) + 2 <= 22002 int maxN = 22002; int[] weight = new int[maxN]; int[] value = new int[maxN]; int p=1;//数组索引 for (int i = 1; i <= n; i++) { int W = in.nextInt(); int V = in.nextInt(); int S = in.nextInt(); for(int k=1;k<S;k*=2){ weight[p] = k*W; value[p] = k*V; S-=k; p++; } if(S>0){//剩余 weight[p] = S*W; value[p] = S*V; p++; } } System.out.println(maxValueBinary(value,weight,n,v)); } /** * * @param value * @param weight * @param n * @param v * @return */ private static int maxValueBinary(int[] value, int[] weight, int n, int v) { //构建好数组就变成了01背包问题 int[] dp = new int[v+1]; for(int i=1;i<=n;i++){ for(int j=v;j>=weight[i];j--){ dp[j] = Math.max( dp[j], dp[j-weight[i]] + value[i]); } } return dp[v]; } }