01背包问题

125 阅读3分钟
  • 问题背景

    • 一个容量是V的背包和N件物品,每件物品的体积是vi,价值是wi,每件物品只能使用一次,求能装走物品的最大价值
  • Dp分析

Snipaste_2023-03-20_20-44-24.png

  • 状态表示

    • 背包问题一般是用二维的状态表示f(i,j)
    • f(i, j)表示的是哪一个集合:所有满足如下条件的选法的集合
      • 只从前i个物品中选
      • 选出来的物品的总体积小于等于j
    • f(i, j)存的是什么属性:MaxMin数量;在这里f(i,j)存的应该是最大值,即所有满足这种选法的最大值
  • 状态计算:f(i,j)可以怎么算出来?

    • 状态计算一般表示的是集合的划分
      • 划分的原则
        • 不重:一个元素只能出现在一个子集中(不一定所有情况都要满足,求最大值重复也无所谓)
        • 不漏:所有元素都要在子集中出现(任何情况都要满足)
    • 01背包问题一般将集合划分为两个子集
      • 不含i的选法:从从1 ~ i - 1中选,总体积小于等于j
        • f(i - 1, j)
      • i的选法:从1 ~ i中选,并且i一定要选上,总体积小于等于j
        • f(i - 1, j - vi) + wi
        • 这种选法只有当 j >= vi 时才有意义
    • 因此 f(i, j) = max( f(i - 1, j), f(i - 1, j - vi) + wi )
  • 代码

    • //初始化f[0][0 ~ m]都是0,所以省略
      for (int i = 1; i <= n; i++) {
          for (int j = 0; j <= m; j++) {
              //第一个子集
              f[i][j] = f[i - 1][j];
              //第二个子集
              if (j >= V[i]) {
                  f[i][j] = Math.max(f[i][j], f[i - 1][j - V[i]] + W[i]);
              }
          }
      }
      
  • 优化:将二维转换成一维

    • 观察状态计算的过程,可以发现两个特点:

      • f( i ) 层的计算只用到了 **f(i - 1)**层
      • f( i ) 层中的 j 的计算只用到了**f(i - 1)**层中的 jj - vi
    • 因此可以做如下优化

      • i 这一维直接优化掉,i 的每一层循环中的 j 都在原先的 j 上进行覆盖
      • j 的每一层循环中要注意 ,在计算 j 时要用到当前上一层 jj - vi ,由于j - vi < j,所以在这一层循环中要从后往前遍历,防止 j - vi 被提前更新
    • 代码

      • //初始化f[0][0 ~ m]都是0,所以省略
        for (int i = 1; i <= n; i++) {
            for (int j = m; j >= V[i]; j--) {
                f[j] = Math.max(f[j], f[j - V[i]] + W[i]);
            }
        }
        

01 01背包问题

  • 题目

Snipaste_2023-03-20_20-46-13.png

  • 题解1
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 1010;
    public static int[] V = new int[N];
    public static int[] W = new int[N];
    //只从前i个物品中选,选出来的物品的总体积小于等于j,满足这两个条件的价值的最大值
    public static int[][] f = new int[N][N];
    public static int n, m;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        String[] str1 = br.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        for (int i = 1; i <= n; i++) {
            String[] str2 = br.readLine().split(" ");
            V[i] = Integer.parseInt(str2[0]);
            W[i] = Integer.parseInt(str2[1]);
        }

        //初始化f[0][0 ~ m]都是0,所以省略
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= m; j++) {
                //第一个子集
                f[i][j] = f[i - 1][j];
                //第二个子集
                if (j >= V[i]) {
                    f[i][j] = Math.max(f[i][j], f[i - 1][j - V[i]] + W[i]);
                }
            }
        }
        pw.println(f[n][m]);
        br.close();
        pw.close();
    }
}
  • 题解2
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 1010;
    public static int[] V = new int[N];
    public static int[] W = new int[N];
    //只从前i个物品中选,选出来的物品的总体积小于等于j,满足这两个条件的价值的最大值
    public static int[] f = new int[N];
    public static int n, m;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        String[] str1 = br.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        for (int i = 1; i <= n; i++) {
            String[] str2 = br.readLine().split(" ");
            V[i] = Integer.parseInt(str2[0]);
            W[i] = Integer.parseInt(str2[1]);
        }

        //初始化f[0][0 ~ m]都是0,所以省略
        for (int i = 1; i <= n; i++) {
            for (int j = m; j >= V[i]; j--) {
                f[j] = Math.max(f[j], f[j - V[i]] + W[i]);
            }
        }
        pw.println(f[m]);
        br.close();
        pw.close();
    }
}