多重背包问题

135 阅读2分钟
  • 问题背景

    • 一个容量是V的背包和N种物品,每种物品的体积是vi,价值是wi,每种物品最多有si个,求能装走物品的最大价值
  • Dp分析

Snipaste_2023-03-21_20-04-22.png

  • 状态表示

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

    • 多重背包问题将集合划分为s[i]+1个子集
      • 每次选择k个第i个物品,k0 取到 k <= si && k * vi <= j
      • f(i, j) = f(i - 1, j - vi * k) + wi * k
    • 因此 f(i, j) = max( f(i, j), f(i - 1, j - vi * k) + wi * k )
  • 代码

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

    • 将多重背包问题优化成01背包问题

      • 每种物品最多有 si
      • 如果我们将每种物品按照2的幂次进行打包:1, 2, 4, 8, ……, 2^k^, c;其中 2^k+1^>si, 2^k^+c == si
      • 按照这种打包方式可以发现每种物品的选择数量都可以从上述的包裹中凑出来
      • 而每种包裹只能选择一次,这就优化成了01背包问题
      • 时间复杂度从O(n * m * s)优化成了O(n * m * log(s)
    • 打包方式

      • int cnt = 0; //记录包裹数量
        for (int i = 1; i <= n; i++) {
            String[] str2 = br.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);  //这种物品的体积
            int b = Integer.parseInt(str2[1]);  //这种物品的价值
            int s = Integer.parseInt(str2[2]);  //这种物品的价值
            int k = 1;
            while (k <= s) {
                cnt++;
                V[cnt] = a * k;
                W[cnt] = b * k;
                s -= k;
                k *= 2;
            }
            if (s > 0) {
                cnt++;
                V[cnt] = a * s;
                W[cnt] = b * s;
            }
        }
        //包裹一共有cnt个
        n = cnt;
        

练习

01 多重背包问题 I

  • 题目

Snipaste_2023-03-21_23-42-21.png

  • 题解
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];
    public static int[] S = 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]);
            S[i] = Integer.parseInt(str2[2]);
        }

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

02 多重背包问题 II

  • 题目

Snipaste_2023-03-21_23-43-36.png

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

public class Main {
    public static final int N = 11010;
    public static final int M = 2010;
    public static int[] V = new int[N];
    public static int[] W = new int[N];
    //只从前i个物品中选,选出来的物品的总体积小于等于j,满足这两个条件的价值的最大值
    public static int[] f = new int[M];
    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]);

        int cnt = 0; //记录包裹数量
        for (int i = 1; i <= n; i++) {
            String[] str2 = br.readLine().split(" ");
            int a = Integer.parseInt(str2[0]);  //这种物品的体积
            int b = Integer.parseInt(str2[1]);  //这种物品的价值
            int s = Integer.parseInt(str2[2]);  //这种物品的数量
            int k = 1;
            while (k <= s) {
                cnt++;
                V[cnt] = a * k;
                W[cnt] = b * k;
                s -= k;
                k *= 2;
            }
            if (s > 0) {
                cnt++;
                V[cnt] = a * s;
                W[cnt] = b * s;
            }
        }
        //包裹一共有cnt个
        n = cnt;

        //初始化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();
    }
}