分组背包问题

129 阅读3分钟
  • 问题背景

    • 一个容量是V的背包和N组物品,每组物品有si个,每件物品的体积是vij,价值是wij,同一组内的物品最多只能选一个,求能装走物品的最大价值
  • Dp分析 Snipaste_2023-03-22_18-23-41.png

    • 状态表示

      • 二维状态表示f(i,j)

      • f(i,j)表示的是哪一个集合:所有满足如下条件的选法集合

        • 只从前i个物品中选
        • 选出来的物品的总体积小于等于j
      • f(i,j)存的是什么属性:MaxMin数量;在这里f(i,j)存的应该是最大值,即所有满足这种选法的最大值

    • 状态计算:f(i,j)可以怎么算出来?

      • 分组背包问题将集合划分为s[i]+1个子集
        • 不含第**si**组物品的选法
          • f(i, j) = f(i - 1, j)
        • 含第**si**组物品的选法
          • f(i, j) = max( f(i - 1, j - vik) + wik )
          • 这种选法只有当 j >= vik 时才有意义
      • 因此 f(i, j) = max( f(i - 1, j), f(i - 1, j - vik) + wik )
    • 代码

      • //初始化f[0][0 ~ m]都是0,所以省略
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= m; j++) {
                //不选第i组物品的情况
                f[i][j] = f[i - 1][j];
                //选第i组物品的情况
                for (int k = 1; k <= S[i]; k++) {
                    if (j >= V[i][k]) {
                        f[i][j] = Math.max(f[i][j], f[i - 1][j - V[i][k]] + W[i][k]);
                    }
                }
            }
        }
        
  • 优化:将二维转换成一维

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

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

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

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

练习

  • 题目

Snipaste_2023-03-22_19-05-02.png

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

public class Main {
    public static final int N = 110;
    public static int[][] V = new int[N][N];
    public static int[][] W = new int[N][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++) {
            S[i] = Integer.parseInt(br.readLine());
            for (int j = 1; j <= S[i]; j++) {
                String[] str2 = br.readLine().split(" ");
                V[i][j] = Integer.parseInt(str2[0]);
                W[i][j] = Integer.parseInt(str2[1]);
            }
        }

        //初始化f[0][0 ~ m]都是0,所以省略
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= m; j++) {
                //不选第i组物品的情况
                f[i][j] = f[i - 1][j];
                //选第i组物品的情况
                for (int k = 1; k <= S[i]; k++) {
                    if (j >= V[i][k]) {
                        f[i][j] = Math.max(f[i][j], f[i - 1][j - V[i][k]] + W[i][k]);
                    }
                }
            }
        }
        pw.println(f[n][m]);
        br.close();
        pw.close();
    }
}
  • 题解2
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 110;
    public static int[][] V = new int[N][N];
    public static int[][] W = new int[N][N];
    public static int[] S = 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++) {
            S[i] = Integer.parseInt(br.readLine());
            for (int j = 1; j <= S[i]; j++) {
                String[] str2 = br.readLine().split(" ");
                V[i][j] = Integer.parseInt(str2[0]);
                W[i][j] = Integer.parseInt(str2[1]);
            }
        }

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