背包DP之分组背包
背包DP之分组背包
- 一、分组背包基础认知
- 1.1 问题定义
- 1.2 核心特征
- 二、分组背包的状态设计与递推
- 2.1 状态定义
- 2.2 递推关系
- 2.3 遍历顺序
- 三、代码实现
- 3.1 基础二维DP实现
- 3.2 空间优化:一维DP实现
- 优化说明:
- 四、分组背包的变种与应用
- 4.1 变种问题
- 4.2 应用场景
- 五、时间复杂度与优化
- 5.1 时间复杂度
- 5.2 优化技巧
分组背包在基础背包的基础上增加了“分组”约束——物品被划分为若干组,每组中最多选择一个物品,这种模型广泛应用于“互斥选择”场景(如“从多个套餐中选一个”“从多类工具中选一种”)。
一、分组背包基础认知
1.1 问题定义
给定m
组物品,每组物品包含若干个物品;每个物品有重量w[i][k]
和价值v[i][k]
(i
为组号,k
为组内物品序号);背包容量为C
。要求:
- 每组最多选择一个物品(可以不选)。
- 在总重量不超过
C
的前提下,最大化选择物品的总价值。
示例:
- 输入:
m=2
组物品,C=5
- 第1组:
[(w=2, v=3), (w=3, v=5)]
- 第2组:
[(w=1, v=2), (w=4, v=6)]
- 第1组:
- 输出:
8
(选择第1组的(3,5)
和第2组的(1,2)
,总重量3+1=4≤5,总价值5+2=7?不,最优为第1组(2,3)
和第2组(4,6)
,总重量2+4=6>5;正确最优:第1组(3,5)
和第2组(1,2)
总价值7,或第2组(4,6)
和第1组不选,总价值6 → 实际最优为7)
1.2 核心特征
- 分组约束:物品按组划分,组内物品互斥(选了A就不能选同组的B)。
- 可选性:每组可选择一个物品或不选(区别于“每组必须选一个”的变种)。
- 与0/1背包的关联:可视为0/1背包的扩展——0/1背包中每组只有一个物品,分组背包中每组有多个物品。
分组背包的本质是“在组内互斥约束下的物品选择优化”,其解法是0/1背包的自然延伸。
二、分组背包的状态设计与递推
2.1 状态定义
沿用背包问题的经典状态设计,聚焦“容量”和“组数”两个维度:
- 设
dp[i][j]
表示“考虑前i
组物品,背包容量为j
时的最大价值”。 - 目标是
dp[m][C]
(考虑所有m
组,容量C
时的最大价值)。
2.2 递推关系
对于第i
组,有两种选择:不选该组任何物品或选该组的某一个物品。
-
不选第
i
组:价值与前i-1
组相同 →
dp[i][j] = dp[i-1][j]
-
选第
i
组的第k
个物品(需满足j ≥ w[i][k]
):
价值 = 前i-1
组在容量j - w[i][k]
时的价值 + 当前物品价值 →
dp[i][j] = max(dp[i][j], dp[i-1][j - w[i][k]] + v[i][k])
综合两种情况,递推公式为:
dp[i][j] = max(dp[i-1][j], max{ dp[i-1][j - w[i][k]] + v[i][k] | 第i组的所有物品k且j ≥ w[i][k] })
2.3 遍历顺序
与0/1背包类似,为避免同一组物品被多次选择,需按以下顺序遍历:
- 外层遍历组:从第1组到第
m
组(确保每组只处理一次)。 - 中层遍历容量:从
C
逆序到0(避免同一组内的物品被重复选择)。 - 内层遍历组内物品:对当前组的每个物品,更新容量
j
的最大价值。
三、代码实现
3.1 基础二维DP实现
public class GroupKnapsack {/*** 分组背包基础实现(二维DP)* @param m 组数* @param C 背包容量* @param groups 物品组(groups[i]是第i组的物品列表,每个物品为{w, v})* @return 最大价值*/public int maxValue(int m, int C, List<List<int[]>> groups) {// 二维DP数组:dp[i][j] = 前i组,容量j时的最大价值int[][] dp = new int[m + 1][C + 1];// 遍历每组物品(i从1到m)for (int i = 1; i <= m; i++) {List<int[]> group = groups.get(i - 1); // 第i组(0基索引)// 遍历容量(逆序,避免同一组物品重复选择)for (int j = C; j >= 0; j--) {// 情况1:不选第i组,价值继承前i-1组dp[i][j] = dp[i - 1][j];// 情况2:选第i组的某个物品for (int[] item : group) {int w = item[0];int v = item[1];if (j >= w) { // 容量足够dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - w] + v);}}}}return dp[m][C];}public static void main(String[] args) {GroupKnapsack solver = new GroupKnapsack();int m = 2; // 2组int C = 5; // 容量5// 初始化物品组:第0组[(2,3), (3,5)],第1组[(1,2), (4,6)]List<List<int[]>> groups = new ArrayList<>();groups.add(Arrays.asList(new int[]{2, 3}, new int[]{3, 5}));groups.add(Arrays.asList(new int[]{1, 2}, new int[]{4, 6}));System.out.println(solver.maxValue(m, C, groups)); // 输出7(3+5组选3,5;1+2组选1,2 → 5+2=7)}
}
3.2 空间优化:一维DP实现
观察二维DP可知,dp[i][j]
仅依赖dp[i-1][j]
(上一组的状态),因此可压缩为一维数组:
- 用
dp[j]
表示“当前处理到第i
组,容量j
时的最大价值”。 - 遍历组和容量的顺序不变,仅需用当前组的结果覆盖上一组的结果。
public int maxValueOptimized(int m, int C, List<List<int[]>> groups) {// 一维DP数组:dp[j] = 容量j时的最大价值(滚动更新)int[] dp = new int[C + 1];// 遍历每组物品for (int i = 0; i < m; i++) {List<int[]> group = groups.get(i);// 逆序遍历容量(关键:避免同一组物品被多次选择)for (int j = C; j >= 0; j--) {// 遍历组内物品(更新当前容量的最大价值)for (int[] item : group) {int w = item[0];int v = item[1];if (j >= w) {dp[j] = Math.max(dp[j], dp[j - w] + v);}}}}return dp[C];
}
优化说明:
- 空间复杂度从
O(m×C)
降至O(C)
,尤其适合组数m
较大的场景。 - 逆序遍历容量是核心:确保处理第
i
组时,dp[j - w]
仍是“前i-1
组”的状态,避免同一组内的物品被重复选择(若正序遍历,可能多次选择同一组的多个物品)。
四、分组背包的变种与应用
4.1 变种问题
-
每组至少选择一个物品:
- 初始化
dp[0...C]
为-∞
(表示不可行),仅dp[0] = 0
。 - 递推时确保每组至少选择一个物品(避免“不选”的情况)。
- 初始化
-
组内物品可选择多个(但不超过组内数量):
- 结合多重背包的“二进制优化”,将组内物品拆分为“可选k个”的组合。
-
有依赖的分组背包:
- 组内物品存在依赖(如选择A才能选择B),需先处理依赖关系,再按分组背包逻辑求解。
4.2 应用场景
- 套餐选择:从多个套餐(每组)中选一个,如“外卖套餐”“旅游套餐”。
- 工具选型:从多类工具(每组)中选一种,如“编程语言(Python/Java)”“数据库(MySQL/PostgreSQL)”。
- 资源分配:有限预算下从多类项目(每组)中选一个投资,最大化收益。
五、时间复杂度与优化
5.1 时间复杂度
设组数为m
,每组物品数为k
(平均),背包容量为C
:
- 基础实现与优化实现的时间复杂度相同:
O(m×k×C)
。 - 当
m=100
,k=10
,C=1000
时,总操作次数为100×10×1000=10^6
,效率可接受。
5.2 优化技巧
- 组内物品剪枝:若组内存在物品A(w1, v1)和物品B(w2, v2),且
w1 ≤ w2
且v1 ≥ v2
,则B为无效物品(选A更优),可删除B减少遍历次数。 - 容量上限优化:对每组物品,计算组内最小总重量,容量小于该值时可跳过处理。
- 并行计算:对独立的组(无依赖),可并行处理(实际应用中较少使用,因Java多线程开销可能抵消收益)。
总结
分组背包是处理“互斥选择”问题的高效模型,其核心是“按组遍历+逆序容量+组内物品选择”:
- 状态定义延续背包问题的经典设计,聚焦“组数”和“容量”。
- 逆序遍历容量是避免组内物品重复选择的关键。
- 最终结果需取所有容量的最大值(而非仅
dp[C]
)。
That’s all, thanks for reading~~
觉得有用就点个赞
、收进收藏
夹吧!关注
我,获取更多干货~