01背包、完全背包

01背包、完全背包

DP

01背包

有 $N$ 件物品和一个容量是 $V$ 的背包。每件物品只能使用一次。

第 $i$ 件物品的体积是 $vi$,价值是 $w_i$。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,$N,V$,用空格隔开,分别表示物品数量和背包容积。

接下来有 $N$ 行,每行两个整数 $v_i,w_i$,用空格隔开,分别表示第 $i$ 件物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

$0<N,V≤1000$
$0<v_i,w_i≤1000$

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8
public class Beg_01 {
  final static int N = 10010;
  final static int[] w = new int[N], v = new int[N];
  // 未优化版
  final static int[][] f = new int[N][N];

  // 优化版
  final static int[] g = new int[N];

  public static void main(String[] args) {
    Scanner cin = new Scanner(System.in);
    int n = cin.nextInt(), m = cin.nextInt();

    for (int i = 1; i <= n; i++) {
      v[i] = cin.nextInt(); w[i] = cin.nextInt();
    }

    // 朴素版
    prime(n, m);

    // 优化版
    optimization(n, m);
  }

  private static void prime(int n, int m) {
    // i 指向第 i 个物品
    for (int i = 1; i <= n; i++) {
      // j 指向容量
      for (int j = 1; j <= m; j++) {
        // 没有将第 i 个物品加入背包的情况
        f[i][j] = f[i - 1][j];
        // 将第 i 个物品加入背包的情况
        if (v[i] <= j) f[i][j] = Math.max(f[i][j], f[i - 1][j - v[i]] + w[i]);
      }
    }
    System.out.println(f[n][m]);
  }	

  private static void optimization(int n, int m) {
    // i 指向第 i 个物品
    for (int i = 1; i <= n; i++) {
      // j 指向容量
      for (int j = m; j >= v[i]; j--) {
        // 将第 i 个物品加入背包的情况
        g[j] = Math.max(g[j], g[j - v[i]] + w[i]);
      }
    }
    System.out.println(g[m]);
  }
}

完全背包

有 $N$ 种物品和一个容量是 $V$ 的背包,每种物品都有无限件可用。

第 $i$ 种物品的体积是 $v_i$,价值是 $w_i$。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,$N,V$,用空格隔开,分别表示物品种数和背包容积。

接下来有 $N$ 行,每行两个整数 $v_i,w_i$,用空格隔开,分别表示第 $i$ 种物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

$0<N,V≤1000$
$0<v_i,w_i≤1000$

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

10
/**
 * Description: 完全背包问题
 *              求在前 i 个物品中,在不超过 j 容量的前提下的组合最大值
 */
public class Beg_Complete {
  final static int N = 10010;
  final static int[] v = new int[N], w = new int[N];

  final static int[][] f = new int[N][N];

  final static int[] g = new int[N];

  public static void main(String[] args) {
    int n = Tools.getInt(), m = Tools.getInt();

    for (int i = 1; i <= n; i++) {
      v[i] = Tools.getInt(); w[i] = Tools.getInt();
    }

    prime(n, m);

    optimization(n, m);

    ultimateOptimization(n, m);
  }

  private static void prime(int n, int m) {
    for (int i = 1; i <= n; i++)
      for (int j = 0; j <= m; j++)
        for (int k = 0; k * v[i] <= j; k++)
          f[i][j] = Math.max(f[i][j], f[i - 1][j - v[i] * k] + k * w[i]);

    System.out.println(f[n][m]);
  }

  private static void optimization(int n, int m) {
    for (int i = 1; i <= n; i++)
      for (int j = 0; j <= m; j++) {
        f[i][j] = f[i - 1][j];
        if (v[i] <= j) f[i][j] = Math.max(f[i][j], f[i][j - v[i]] + w[i]);
      }

    System.out.println(f[n][m]);
  }

  private static void ultimateOptimization(int n, int m) {
    for (int i = 1; i <= n; i++)
      for (int j = v[i]; j <= m; j++) {
        g[j] = Math.max(g[j], g[j - v[i]] + w[i]);
      }

    System.out.println(g[m]);
  }
}

优化过程

令 $j$ 等于 $x + k * v[i]$ 则有 $$f[i][j] = f[i][x + k * v[i]]$$

从题意出发,有 ① $$f[i][x + k * v[i]] = max(f[i - 1][x + k * v[i]], f[i - 1][x + (k - 1) * v[i]] + w[i], ···, f[i - 1][x] + k * w[i])$$

同理可得 ② $$f[i][x + (k - 1) * v[i]] = max(f[i - 1][x + (k - 1) * v[i]], f[i - 1][x + (k - 2) * v[i]] + w[i], ···, f[i - 1][x] + k * w[i])$$

那么整理可得 ③ $$f[i][x + k * v[i]] = max(f[i - 1][x + k * v[i]], f[i][x + (k - 1) * v[i]] + w[i])$$

故 $$f[i][j] = max(f[i - 1][j], f[i][j - v[i]] + w[i])$$

优化为一维,将 $i$ 去除,可得 ④ $$f[j] = max(f[j], f[j - v[i]] + w[i])$$

对比可知 $③ == ④$

对比

  • 类似 01背包问题,但 01背包问题要求只选择一次,故只能由后向前遍历,将下一层的数据覆盖上一层的数据;

  • 完全背包问题则是要求选择多次,可能出现使用同一层的数据进行更新,故只需从前往后遍历,使之覆盖数据就行

参考