说明:
本文所讲内容摘录自崔添翼:背包九讲,并对其中的数学内容和一些较为复杂的内容进行了删减,增加了基础的例题,只是面向初学者或者不需要深入理解背包及其衍生问题的读者,如果有能力并且有意愿加深理解,本文可能会对您形成误导,请移步崔添翼:背包九讲.
分组背包问题
题目
有 N
件物品和一个容量为 V
的背包。第 i
件物品的费用是
C
i
C_i
Ci,价值是
W
i
W_i
Wi。这些物品被划分为 K
组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
基本思路
这个问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件都不选。也就是说设
F
[
k
,
v
]
F[k, v]
F[k,v] 表示前 k
组物品花费费用 v
能取得的最大权值(收益),则有:
F
[
k
,
v
]
=
m
a
x
{
F
[
k
−
1
,
v
]
,
F
[
k
−
1
,
v
−
C
i
]
+
W
i
∣
i
t
e
m
i
∈
g
r
o
u
p
k
}
F[k, v] = max\{F[k − 1, v], F[k − 1, v −C_i] +W_i \space|\space item \space i ∈ group\space k\}
F[k,v]=max{F[k−1,v],F[k−1,v−Ci]+Wi ∣ item i∈group k}
注意哦❗k
代表组,v
代表物品的容量,这个group
隐含了一层意思就是对于这个组内的每个元素都要遍历一遍的,这个公式代表的是一个三层循环,具体的转移方程还是看伪代码比较好理解一些.
上述问题的伪代码
int N, V;
// cv[i][j][0]是放第i件物品消耗的费用,cv[i][j][1]是放第i件物品的收益价值,其中i从1开始
// GN是每个组内的元素的数量
int[][][] cv = new int[N + 1][GN][2];
int dp[][] = new int[N + 1][V + 1];
dp[0][0] = 0;
for (int i = 1; i <= N; i++) {
for (int j = 0; j <= V; j++) {
for (int k = 0; k < cv[i].length; k++) {
if(cv[i][k][0]<=j){
dp[i][j] = Math.max(dp[i-1][j],Math.max(dp[i][j], dp[i - 1][j - cv[i][k][0]] + cv[i][k][1]));
}else{
dp[i][j] = Math.max(dp[i-1][j],dp[i][j]);
}
}
}
}
return dp[N][V];
注意❗: 外层i
枚举物品的数量,内层j
枚举背包的容量,最内层k
枚举组内的每个元素
注意❗❗❗: 转移方程为dp[i][j] = Math.max(dp[i-1][j],Math.max(dp[i][j], dp[i - 1][j - cv[i][k][0]] + cv[i][k][1]));
(物品容量小于背包容量)和dp[i][j] = Math.max(dp[i-1][j],dp[i][j]);
(物品容量大于背包容量)
一个简单有效的优化
略🚮🤐(太菜了,慢慢来)
相关题目练习
题目URL
有 N
组物品和一个容量是 V
的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是
v
i
j
v_{ij}
vij,价值是
w
i
j
w_{ij}
wij,其中 i
是组号,j
是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N
,V
,用空格隔开,分别表示物品组数和背包容量。
接下来有 N
组数据:
- 每组数据第一行有一个整数
S
i
S_i
Si,表示第
i
个物品组的物品数量; - 每组数据接下来有
S
i
S_i
Si 行,每行有两个整数
v
i
j
,
w
i
j
v_{ij},w_{ij}
vij,wij,用空格隔开,分别表示第
i
个物品组的第j
个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围
0
<
N
,
V
≤
1000
<
N
,
V
≤
100
0<N,V≤1000<N,V≤100
0<N,V≤1000<N,V≤100
0
<
S
i
≤
1000
<
S
i
≤
100
0<S_i≤1000<S_i≤100
0<Si≤1000<Si≤100
0
<
v
i
j
,
w
i
j
≤
1000
<
v
i
j
,
w
i
j
≤
100
0<v_{ij},w_{ij}≤1000<v_{ij},w_{ij}≤100
0<vij,wij≤1000<vij,wij≤100
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例:
8
题目解法
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 物品的组数量
int N = sc.nextInt();
// 背包的容量
int V = sc.nextInt();
// 每个物品的体积和价值
int[][][] gvw = new int[N + 1][][];
for (int i = 1; i <= N; i++) {
// 第 i 个物品组的物品数量
int Si = sc.nextInt();
gvw[i] = new int[Si][2];
for (int j = 0; j < Si; j++) {
// 体积
gvw[i][j][0] = sc.nextInt();
// 价值
gvw[i][j][1] = sc.nextInt();
}
}
int dp[][] = new int[N + 1][V + 1];
// 不要求完全装满背包,初始化全设置为0,因为java默认int数组为0所以象征性的初始化第一个
dp[0][0] = 0;
// 枚举第i件物品
for (int i = 1; i <= N; i++) {
// 枚举背包的容量j
for (int j = 0; j <= V; j++) {
// 枚举选择第i种物品的数量k
for (int k = 0; k < gvw[i].length; k++) {
if (gvw[i][k][0] <= j) {
// 在组之间有选择或不选择当前组这两种情况,在组内也有两种情况,
// 分别是选择当前组内的当前元素和不选择当前组内的当前元素
// 在组之间,不选择当前的组,问题转化成i-1个组容量为j的背包最多能装下物品的收益即:dp[i-1][j]
// 在组之间,选择当前的组,问题转化成第i个组内选择和不选择第k个物品的问题,描述如下:
// 在组内不选择第k个物品时,目前的最大收益是dp[i][j](可能选择了某个物品,也可能没有选择某个物品),
// 在组内选择第k个物品时,收益是通过没选择当前组的收益转移而来的,
// 选择第k个物品消耗的容量是gcw[i][k][0],问题转化成i-1个组,
// 容量为j-gcw[i][k][0]的背包最多能装下的物品的最大收益,是dp[i-1][j-gvw[i][k][0]]
dp[i][j] = Math.max(dp[i - 1][j], Math.max(dp[i][j], dp[i - 1][j - gvw[i][k][0]] + gvw[i][k][1]));
} else {
// 这里需要注意的是如果背包的容量小于当前组内物品的体积,
// 那么需要比较不选当前组的任何物品时的收益与
// 当前组内的收益作比较(当前组内可能已经选择了某些,也可能没有选择,没有选择时值是dp[i - 1][j])
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j]);
}
}
}
}
// 返回N件物品,背包容量为V的装入的最大价值
System.out.println(dp[N][V]);
}
}
参考文献