背包问题_2.pdf

Acwing 2. 01背包问题

01背包,不超过,最大价值
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
00输入样例

  1. 4 5
  2. 1 2
  3. 2 4
  4. 3 4
  5. 4 5

输出样例:

  1. 8

二维版本

  1. f[i, j] 表示只从前i个物品中选,且总体积不超过v的所有选法的集合,其属性是价值最大值
  2. 状态计算:只考虑前i件物品的情况下计算在所有体积下的选法的最大价值

考虑第i件物品,当前体积为j

  • j < v[i],不能选当前物品 f[i][j] = f[i - 1][j]
  • j >= v[i] ,当前物品可选可不选,如果不选 f[i, j] = f[i - 1][j] ,如果选择 f[i, j] = f[i - 1][j - v[i]] + w[i],结果为两者取Max
    1. 初始状态

i == 0时,意思是从前0件物品中选且总体积不超过j的最大价值,很显然,结果是0
Arrays.fill(f[0], 0) 可写可不写。

  1. import java.util.*;
  2. public class Main {
  3. public static void main(String[] args) {
  4. Scanner sc = new Scanner(System.in);
  5. int n = sc.nextInt(), m = sc.nextInt();
  6. int[] v = new int[n + 1], w = new int[n + 1];
  7. //读入每一件物品的体积和价值
  8. for (int i = 1; i <= n; i++) {
  9. v[i] = sc.nextInt();
  10. w[i] = sc.nextInt();
  11. }
  12. //状态定义
  13. int[][] f = new int[n + 1][m + 1];
  14. //状态计算
  15. //外层遍历物品
  16. for (int i = 1; i <= n; i++) {
  17. //内层遍历体积
  18. for (int j = 0; j <= m; j++) {
  19. f[i][j] = f[i - 1][j];
  20. if (j >= v[i]) f[i][j] = Math.max(f[i][j], f[i - 1][j - v[i]] + w[i]);
  21. }
  22. }
  23. System.out.println(f[n][m]);
  24. }
  25. }

一维版本

去掉 i 这一维,效果相当于滚动数组
f[j] = Math.max(f[j], f[j - v[i]] + w[i]

为什么一维情况下枚举背包容量需要逆序?
在二维情况下,状态f``[i][j]是由上一轮i`` - 1的状态得来的,f[i][j]f[i - 1][j]是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。

  1. for(int i = 1; i <= n; i++)
  2. for(int j = m; j >= 0; j--)
  3. {
  4. if(j < v[i])
  5. f[i][j] = f[i - 1][j]; // 优化前
  6. f[j] = f[j]; // 优化后,该行自动成立,可省略。
  7. else
  8. f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]); // 优化前
  9. f[j] = max(f[j], f[j - v[i]] + w[i]); // 优化后
  10. }
  11. //进一步,只有背包容量j >= v[i]时f[j]才会被更新,所以可进一步优化为
  12. for(int i = 1; i <= n; i++)
  13. {
  14. for(int j = m; j >= v[i]; j--)
  15. f[j] = max(f[j], f[j - v[i]] + w[i]);
  16. }
  17. //当执行完循环结构后,由于已经决策了所有物品,f[j]就是所有物品背包容量 j 下的最大价值。即一维f[j]等价于二维f[n][j]
  1. //整体代码
  2. import java.util.*;
  3. public class Main {
  4. public static void main(String[] args) {
  5. Scanner sc = new Scanner(System.in);
  6. int n = sc.nextInt(), m = sc.nextInt();
  7. int[] v = new int[n + 1], w = new int[n + 1];
  8. for (int i = 1; i <= n; i++) {
  9. v[i] = sc.nextInt();
  10. w[i] = sc.nextInt();
  11. }
  12. int[] f = new int[m + 1];
  13. for (int i = 1; i <= n; i++) {
  14. for (int j = m; j >= v[i]; j--)
  15. f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
  16. }
  17. System.out.println(f[m]);
  18. }
  19. }

进一步优化,不需要存储体积和价值,在循环时读入即可

  1. import java.util.*;
  2. public class Main {
  3. public static void main(String[] args) {
  4. Scanner sc = new Scanner(System.in);
  5. int n = sc.nextInt(), m = sc.nextInt();
  6. int[] f = new int[m + 1];
  7. for (int i = 0; i < n; i++) {
  8. int v = sc.nextInt(), w = sc.nextInt();
  9. for (int j = m; j >= v; j--)
  10. f[j] = Math.max(f[j], f[j - v] + w);
  11. }
  12. System.out.println(f[m]);
  13. }
  14. }

01背包求方案数

AcWing 278. 数字组合
01背包,恰好装满,方案数
思路:
状态表示:f[i][j]表示从前i个数中选,总和恰好为j的所有方案的集和
状态属性:集和元素个数
状态计算:考虑第i个数选或不选
f[i][j] = f[i - 1][j] + f[i - 1][j - x],条件是j >= x
初始化:f[0][0] = 1表示从0个数中选总和为0的方案数为1,即什么都不选。

  1. import java.util.*;
  2. public class Main {
  3. static final int N = 110, M = 10010;
  4. static int[][] f = new int[N][M];
  5. static int n, m;
  6. public static void main(String[] args) {
  7. Scanner sc = new Scanner(System.in);
  8. n = sc.nextInt();
  9. m = sc.nextInt();
  10. f[0][0] = 1;
  11. for (int i = 1; i <= n; i++) {
  12. int x = sc.nextInt();
  13. for (int j = 0; j <= m; j++) {
  14. f[i][j] = f[i - 1][j];
  15. if (j >= x)
  16. f[i][j] += f[i - 1][j - x];
  17. }
  18. }
  19. System.out.println(f[n][m]);
  20. }
  21. }
  1. // 去掉物品维
  2. import java.util.*;
  3. public class Main {
  4. static final int M = 10010;
  5. static int[] f = new int[M];
  6. static int n, m;
  7. public static void main(String[] args) {
  8. Scanner sc = new Scanner(System.in);
  9. n = sc.nextInt();
  10. m = sc.nextInt();
  11. f[0] = 1;
  12. for (int i = 1; i <= n; i++) {
  13. int x = sc.nextInt();
  14. for (int j = m; j >= x; j--) {
  15. f[j] += f[j - x];
  16. }
  17. }
  18. System.out.println(f[m]);
  19. }
  20. }

其它例题

416. 分割等和子集 01背包,恰好装满,是否可行
494. 目标和 01背包,恰好装满,方案数
1049. 最后一块石头的重量 II 01背包,不超过,最大价值
类似于494