背包问题:分组背包问题

198 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

往期

  1. 01背包问题
  2. 完全背包问题
  3. 多重背包问题I
  4. 多重背包问题II
  5. 混合背包问题
  6. 二维费用背包问题

题目

分组背包问题

NN 组物品和一个容量是 VV 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。 每件物品的体积是 vijv_{ij},价值是 wijw_{ij},其中 ii 是组号,jj 是组内编号。

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

输出最大价值。

输入格式

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

接下来有 NN 组数据:

  • 每组数据第一行有一个整数 SiS_{i},表示第 ii 个物品组的物品数量;
  • 每组数据接下来有 SiS_{i} 行,每行有两个整数 vij,wijv_{ij},w_{ij},用空格隔开,分别表示第 ii 个物品组的第 jj 个物品的体积和价值;

输出格式

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

数据范围

0<N,V1000<N,V≤100
0<Si1000<S_{i}≤100
0<vij,wij1000<v_{ij},w_{ij}≤100

输入样例

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

输出样例:

8

解题思路

01背包问题每种物品有两种策略:选或者不选,本题变成了每组物品有两种策略:选择本组的一件或者一件都不选。

  1. 状态定义

dp[i][j]dp[i][j]: 前ii组物品花费费用jj能取得的最大价值

  1. 状态转移方程

dp[i][j]=max[dp[i1][j],dp[i1][jv[i][k]]+w[i][k]]dp[i][j] = max\left[ dp[i-1][j], dp[i-1][j-v[i][k]] + w[i][k]\right]

import java.util.*;

class Main {
    private static final int S = 110;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        
        int N = sc.nextInt();           // 物品组数
        int C = sc.nextInt();           // 背包容量
        
        int[][] v = new int[N+1][S];    // 物品体积
        int[][] w = new int[N+1][S];    // 物品价值
        int[] s = new int[N+1];         // 每组物品数量
        for (int i = 1; i <= N; i++) {
            s[i] = sc.nextInt();
            for (int j = 0; j < s[i]; j++) {
                v[i][j] = sc.nextInt();
                w[i][j] = sc.nextInt();
            }
        }
        
        int[][] dp = new int[N+1][C+1];
        
        for (int i = 1; i <= N; i++) {
            for (int j = 1; j <= C; j++) {
                dp[i][j] = dp[i-1][j];           // 不选第i组物品
                for (int k = 0; k < s[i]; k++) { // 第i组物品中选一件
                    if (j >= v[i][k]) {
                        dp[i][j] = Math.max(dp[i][j], dp[i-1][j-v[i][k]] + w[i][k]);
                    }
                }
            }
        }
        System.out.println(dp[N][C]);
    }
}

空间优化

类似01背包问题中空间优化,可将本题的二维dp数组优化为一维数组,仿照01背包的套路逆向枚举体积

import java.io.*;
import java.util.*;

public class Main {
    private static final int S = 110;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int N = sc.nextInt();            // 物品组数
        int C = sc.nextInt();            // 背包容积

        int[][] v = new int[N+1][S];        // 体积
        int[][] w = new int[N+1][S];        // 价值
        int[] s = new int[N+1];

        for (int i = 1; i <= N; i++) {
            s[i] = sc.nextInt();
            for (int j = 0; j < s[i]; j++) {
                v[i][j] = sc.nextInt();
                w[i][j] = sc.nextInt();
            }
        }

        int[] dp = new int[C+1];
        for (int i = 1; i <= N; i++) {
            for (int j = C; j >= 0; j--) {
                for (int k = 0; k < s[i]; k++) {
                    if(j>=v[i][k]) {
                        dp[j] = Math.max(dp[j], dp[j - v[i][k]] + w[i][k]);
                    }
                }
            }
        }
        System.out.println(dp[C]);
    }
}

Reference

  1. 背包问题九讲

  2. 分组背包问题