点击查看代码
/*
分组背包
题目:https://www.luogu.com.cn/record/267074243
2.讲解算法原理
1.状态表示
f[i][j] 表示:从前i个物品中挑选, 总重量不超过j的情况下, 最大价值----不行(不像之前的背包问题)
f[i][j] 表示:从前i个小组中挑选, 总重量不超过j的情况下, 最大价值
2.状态转移方程
不选 -----> f[i - 1][j]
f[i][j] 选一号 ----> f[i - 1][j - a1] + b1 a-重量, b-价值
选二号------> f[i - 1][j - a2] + b2
............
3.初始化: 全部为0
4.填表顺序: 从上到下每一行
5.最终结果
6.空间优化:第二维必须从大到小
删掉第一维数组即可
*/
#include <iostream>
#include <vector>
using namespace std;
// 定义pair别名,PII.first存物品重量,PII.second存物品价值,简化代码
typedef pair<int, int> PII;
// 数组最大容量:题目中m≤1000、组数≤100,1010足够覆盖所有情况
const int N = 1010;
// 分组存储物品:g[c]表示第c组的所有物品,每个物品用PII(重量, 价值)表示
vector<PII> g[N];
// 二维DP数组:f[i][j] 表示从前i个小组中挑选,总重量不超过j时的最大价值
int f[N][N];
int main(){
// n:物品总数,m:背包最大容量(注意输入顺序是先m后n,匹配题目输入格式)
int n, m;
cin >> m >> n;
// cnt:记录输入的最大组号(避免遍历无物品的空组,提升效率),初始为1(题目中组号从1开始)
int cnt = 1;
// 遍历所有物品,按组号分类存储
for(int i = 1; i <= n; i++){
// a:物品重量,b:物品价值,c:物品所属组号
int a, b ,c;
cin >> a >> b >> c;
// 更新最大组号,确保后续遍历所有有物品的组
cnt = max(cnt, c);
// 将当前物品添加到对应组的列表中
g[c].push_back({a, b});
}
// ========== 分组背包核心DP逻辑 ==========
// 外层循环:遍历前i个小组(从上到下填表,匹配算法原理的填表顺序)
for(int i = 1; i <= cnt; i++){
// 中层循环:遍历背包容量j,从m到0逆序(空间优化的关键,避免同一组选多个物品)
for(int j = m ; j >= 0; j--){
// 状态转移-不选第i组:继承前i-1个小组、容量j的最大价值
f[i][j] = f[i-1][j];
// 内层循环:遍历第i组的所有物品,尝试选择其中一个
for(auto& t: g[i]){
// 取出当前物品的重量和价值
int a = t.first; // 物品重量
int b = t.second; // 物品价值
// 只有当背包容量j≥物品重量时,才有可能选择该物品
if(j >= a){
// 状态转移-选第i组的当前物品:
// 比较「不选当前物品(当前f[i][j])」和「选当前物品(f[i-1][j-a]+b)」,取最大值
// 注:这里用f[i][j]而非f[i-1][j],是为了保留组内前一个物品的最优解,避免覆盖
f[i][j] = max(f[i][j], f[i-1][j-a] + b);
}
}
}
}
// 最终结果:前cnt个小组、背包容量不超过m时的最大价值(匹配算法原理的最终结果)
cout << f[cnt][m] << endl;
return 0;
}