分组背包
1 问题描述
不同于01背包,只有n个物品,现在我们有n组物品,且每组物品最多选一个,问最大价值是什么?
2 5
3
1 1
2 2
3 3
2
1 2
2 3
2 三维解法
类比于01背包的二维写法,我们很容易想到分组背包的三维写法
可以想到状态转移方程如下:
显然
3 二维解法
第i组内,前j个物品,总和前j-1有关,类比01背包,前i个物品总和前i-1个有关,
所以我们可以把组合物品的j维去掉
f[i][w] = max{f[i-1][W-w[i][j]] + v[i][j] ,f[i][w]}
如果去除j这一维后,怎么得到前j个物在容量为W的条件下的最大价值呢?
显然我们应该按这样的顺序求
figure_3.svg webm
保持容量w不变,然后去依次去求组内的前1个物品,前2个物品,…前j个物物品
因为当你求完前1个物品时 f[i][w] 就表示,前i组前1个物品在容量w下的最值, 当你考虑前2个物品时,f[i][w]此时还是前1个物品的值,正好用来更新前2个物品的值
for( int i =1;j<=N;i++){
//初始化j=0时,每个容量都应该是
for(int k = 0 ; k<=C ; k++)
f[i][k] = f[i-1][k];
for( int j =1;j<=total(i);j++){
for(int k = 1 ;k <= C;k++) { // 枚举容量
//不选j的价值
if( k>= w[i][j] )
f[i][w] = max(f[i-1][W-w[i][j]] + v[i][j],f[i][w]);
} // end for j
} //end for k
} // end for i
可以看到竟然有二次for K 循环,代码这显然不是很优雅
for( int i =1;j<=N;i++){
for(int k = 1 ;k <= C;k++) { // 枚举容量
f[i][k] = f[i-1][k];
for( int j =1;j<=total(i);j++){
//不选j的价值
if( k>= w[i][j] )
f[i][w] = max(f[i-1][W-w[i][j]] + v[i][j],f[i][w]);
} // end for j
} //end for k
} // end for i
4 一维解法
同样的,求解前i组的信息,需要前i-1组的信息,能不能把i也压缩掉呢?
当我们求解 前 i j w,f[i][w],它需要的前i-1的信息为f[i-1][w-w[i][j]]
也就是上一组的更小的容量,类比01背包,当我们需要上一次信息的前面,为了避免覆盖信息,把容量倒过来求
figure_4.svg 轮播图
我们可以这样想: 组内的每个物品都要依次处理一遍w5, 然后又依次处理一遍w4,…
把组当成一个物品,那不就相当于,01背包的某个物品从大到小计算容量下的价值吗?
只不过分组背包要得到一个容量下的答案就要把一组内的物品一次处理,所以本质还是01背包.
5 适用模型
分球模型
6 代码模板
虽然代码看起来很长,但是它的核心只有三行:
1:for do
2:for Capacity do
3:for each item in group[i] do
4:Update max value for the current capacity
5:end for
6:end for
7:end for
枚举组i
倒序枚举容量C
处理第i组内的每个物品在当前容量下的最值
这三行代码与01背包的是一样
// desc: 分组背包的模板代码
// author:rainboy
// date: 2023/4/26
#define FOR(i,a,b) for(int i = a ; i<= b; ++i)
#define rFOR(i,a,b) for(int i = a ; i>= b; --i)
// cap 背包的容量
template<int Cap,int MaxStuff>
struct groupKnapsack {
long long f[Cap+5]; //f[i] 在容量为i的条件下得到的最大值
struct stuff { int w,v; }; //一个物品的重量与价值
int stuff_size;
stuff stuffs[MaxStuff+5]; //存一组物品
//清空一组背包
void clear() { stuff_size = 0;}
//添加一个物品
void push(int w,int v){
stuffs[++stuff_size] = {w,v};
}
// 处理一个物品
void deal_one_stuff(int v,int w,int c){
if( c < w ) return; //容量不够
if( f[c] < f[c-w]+v) f[c] = f[c-w]+v;
}
//处理一组物品
void deal_one_group_stuff() {
rFOR(C,Cap,0) // 容量 大->小
FOR(i,1,stuff_size) //枚举物品
deal_one_stuff(stuffs[i].v,stuffs[i].w,C);
}
//得到答案
long long ans(int cap=Cap) {
return f[cap];
}
};
7 练习题目
假设有3组物品:
- 组 1:{物品1,体积3,价值4}, {物品2,体积2,价值3}, {物品3,体积4,价值2}
- 组 2:{物品4,体积1,价值4}, {物品5,体积2,价值5}
- 组 3:{物品6,体积4,价值3}
并且背包的容量限制为 6。 要确定在此约束下选择哪些物品以最大化总价值,可以使用分组背包算法。
数据
3 6
3
3 4
2 3
4 2
2
1 4
2 5
1
4 3
答案
9
完整代码
// desc: 分组背包的模板代码
// author:rainboy
// date: 2023/4/26
#include <bits/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(int i = a ; i<= b; ++i)
#define ROF(i,a,b) for(int i = a ; i>= b; --i)
// cap 背包的容量
template<int Cap,int MaxStuff>
struct groupKnapsack {
long long f[Cap+5]; //f[i] 在容量为i的条件下得到的最大值
struct stuff { int w,v; }; //一个物品的重量与价值
int stuff_size;
stuff stuffs[MaxStuff+5]; //存一组物品
//清空一组背包
void clear() { stuff_size = 0;}
//添加一个物品
void push(int w,int v){
stuffs[++stuff_size] = {w,v};
}
// 处理一个物品
void deal_one_stuff(int v,int w,int c){
if( c < w ) return; //容量不够
if( f[c] < f[c-w]+v) f[c] = f[c-w]+v;
}
//处理一组物品
void deal_one_group_stuff() {
ROF(C,Cap,0) // 容量 大->小
FOR(i,1,stuff_size) //枚举物品
deal_one_stuff(stuffs[i].v,stuffs[i].w,C);
}
//得到答案
long long ans(int cap=Cap) {
return f[cap];
}
};
int n; // n组
int C; //最大容量
// 创建一个 最大容量6,一组物品最多5个的 背包
groupKnapsack<6, 5> mybp;
int main(){
cin >> n >> C;
FOR(i,1,n){
int t;
cin >> t;
mybp.clear();
FOR(j,1,t){
int w,v;
cin >> w >> v;
mybp.push(w,v);
}
mybp.deal_one_group_stuff();
}
cout << mybp.ans(C) << '\n';
return 0;
}
8 相关题解
暂无相关题解