分组背包
【题目】
有
n
n
n组物品和一个容量是
V
V
V的背包。每组物品有若干个,同一组内的物品最多只能选一个。每件物品的体积是
v
i
v_i
vi,价值是
w
i
j
w_{ij}
wij,其中
i
i
i是组号,
j
j
j是组内编号。
求解将哪些物品放入背包,可使物品总体积不超过背包容量,且总价值最大。输出最大价值。
【输入】
第一行有两个整数
n
n
n、
V
V
V,分别表示物品组数和背包容量。
接下来有
n
n
n组数据:
每组数据第一行有一个整数
s
i
s_i
si,表示第
i
i
i个物品组的物品数量;
每组数据接下来有
s
i
s_i
si行,每行有两个整数
v
i
j
v_{ij}
vij、
w
i
j
w_{ij}
wij,分别表示第
i
i
i个物品组的第
j
j
j个物品的体积和价值;
【输出】
输出一个整数,表示最大价值。
输入样例
2 6
2
1 2
2 3
3
1 1
3 5
2 4
输出样例
8
我们使用以下五步来分析这个背包dp问题
1️⃣确定dp数组(dp table)以及下标的含义
2️⃣确定递推公式
3️⃣dp数组如何初始化
4️⃣确定遍历顺序
5️⃣举例推导dp数组
方法一:朴素算法
第一步:确定dp数组(dp table)以及下标的含义
最大价值应该是物品组
i
i
i和背包容量
j
j
j的函数,用
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前
i
i
i组物品,能放入容量为
j
j
j的背包的最大价值。
第二步:确定递推公式
此时我们来思考一下状态转移的过程以便我们来理解递推公式
在前面明确了
d
p
dp
dp数组的含义前
i
i
i组物品,能放入容量为
j
j
j的背包的最大价值。
那么我当前的
f
[
i
]
[
j
]
f[i][j]
f[i][j]只由
f
[
i
−
1
]
[
?
]
f[i-1][?]
f[i−1][?](这里指的是第
i
i
i行的数据来源于第
i
−
1
i-1
i−1行)决定
所以对于第
i
i
i组物品,容量为
j
j
j的背包,有
s
+
1
s+1
s+1种选法(有不选的情况,直接取
f
[
i
−
1
]
[
j
]
f[i-1][j]
f[i−1][j]的值即可)
所以在这其中取最大值即可
m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v 1 ] + w 1 , f [ i − 1 ] [ j − v 2 ] + w 2 , . . . . . , f [ i − 1 ] [ j − v s ] + w s ) max(f[i-1][j],f[i-1][j-v_1]+w_1,f[i-1][j-v_2]+w_2,.....,f[i-1][j-v_s]+w_s) max(f[i−1][j],f[i−1][j−v1]+w1,f[i−1][j−v2]+w2,.....,f[i−1][j−vs]+ws)
// 选入第i组第k个物品,能获取的价值
f[i][j] = max(f[i][j],f[i-1][j-v[i][k]]+w[i][k])
第三步:dp数组如何初始化
第四步:确定遍历顺序
由第二步可知我的遍历顺序为先遍历物品,再遍历体积,最后遍历第i组的每一个物品,从中选择当前状态下价值最大的一个。
第五步:举例推导dp数组
打印dp数组,检查思路是否正确,检查每一个状态的值是否合理
代码:
for(int i=1;i<=n;i++){// 第i组物品
for(int j=1;j<=V;j++){// 体积
for(int k=0;k<=s[i];k++){// 第i组的k个可选物品
// j为当前背包容量,v[i][k]为第i组第k件物品的体积
// 第i组第k件物品的体积大于当前背包容量不选
if(j>=v[i][k]){
// f[i][j]为不选第i组第k个物品,已获取的价值
/** f[i-1][j-v[i][k]]+w[i][k],
为选入第i组第k个物品,能获取的价值 **/
f[i][j] = max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
}
}
}
}
cout<<f[n][V];
对比多重背包的朴素算法
for(int i=1;i<=n;i++){// 物品
for(int j=1;j<=V;j++){// 体积
for(int k=0;k<=s[i];k++){// 决策 (选那些)
if(j>=k*v[i]){
f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
}
}
}
cout<<f[n][V];
多重背包:
时间复杂度可以优化(二进制优化,单调队列优化),
空间复杂度可以优化(二维数组–》一维数组)。
分组背包:
时间复杂度难以优化(分组背包第i组物品的体积和价值各不相同,难以组合或分类),
空间复杂度可以优化(只使用了上一行的数组,这样就可以优化为滚动数组(一维数组))。
for(int i=1;i<=n;i++){// 物品
cin>>s;
for(int j=1;j<=s;j++) cin>>v[j]>>w[j];
// 分组背包 朴素算法 一维
for(int j=V;j>=1;j--){// 体积
for(int k=0;k<=s;k++){// 决策
if(j>=v[k]){
f[j] = max(f[j],f[j-v[k]]+w[k]);
}
}
}
}
cout<<f[V];
逆序的原因:
因为
f
[
j
]
f[j]
f[j]先于
f
[
j
−
v
k
]
f[j-v_k]
f[j−vk]更新,所以
f
[
j
−
v
k
]
f[j-v_k]
f[j−vk]的值等价于
f
[
i
−
1
]
[
j
−
v
k
]
f[i-1][j-v_k]
f[i−1][j−vk]的值。