1.问题描述:
有 N组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
ps:分组背包问题 和 多重背包问题 的根本区别 在于 分组背包问题 中 分组背包是每组内选一种, 而多重背包的 每种 选 k个。
2.问题的解决思路:
2.0 思路概要
解决 dp问题的关键任然在于 状态表示和状态计算,这一点和完全背包问题如出一辙。

其实 我们仔细分析 分组背包 和 多重背包 以及 完全背包 之间的关系,我们发现是不断泛化的过程.
分组背包 其实是 最大的定义

为了方便理解我们仔细看看各类背包之间的定义 以及互相概念意义之间互通的转化
分组背包: 分成若干组,每组内有各种价值,体积的 物品,每组选一个装入 背包
多重背包: 分成若干组,每组内有同种价值,体积的 物品,物品数量题目给定,选择k个该物品进入背包
按照分组背包的方式定义
多重背包: 分成若干组,每组内有同种价值,体积的 物品,物品数量题目给定,(将k个同种物品打包成 一个新物品,新物品的体积,价值是原来的k倍)选择k个该物品 进入背包
完全背包: 分成若干组,每组内有同种价值,体积的 物品,物品数量题目无限,选择若干个该物品进入背包
按照多重背包的方式定义
完全背包: 分成若干组,每组内有同种价值,体积的 物品,物品数量题目无限(为了放入背包,实际上只能放入最多V/v[i]个物品),选择若干个该物品进入背包
2.1 状态表示
状态表示 可以 分栏为 集合定义以及集合属性
集合定义: 根据题意,我们规定集合dp【i】【j】表示 所有只从前0到i组物品种选,而且总体积 不大于j的选法
集合属性:我们定义集合的属性值 为 满足上述要求的集合所有选法中,造成背包价值最大的选法。
2.2 状态计算
集合的划分:
我们把集合dp 【i】【j】划分为 k 个子集,分别表示 背包中 所拥有 第 i 组 物品的第 k 个.

子集的表示:
其中,绿色 框 所表示的 子集意义 则为 所有只从前0到i组物品种选,而且总体积 不大于j的所有选法,这一前提下,不包含第i组物品的选法 ,也就是 所有只从前0到i-1组物品种选,而且总体积 不大于j的所有选法,根据集合定义,我们得知 绿色 子集表示为 dp[i-1][j]

最右侧的 紫色框 表示集合 意义为 所有只从前0到i组物品种选,而且总体积 不大于j的所有选法,这一前提下,包含第i组物品选第k个物品 的选法
这里偷懒了 ,和完全背包的文是一样的
我们依然采取曲线救国的方式:从紫色 集合 中 同时去除第 k个第i组 物品,则这个时候可以紫色框 根据 状态定义,可以表示为
dp[i-1][j - v[i][k] ]
而 这个集合的 属性值(所有选法中的背包最大价值)和去除 物品之前,相差了 第i个物品价值的 w[i][k] ,那么紫色 集合 泛指的包含k个第 i种个物品集合的选法中最大价值可以表示成
dp[i-1][j - v[i][k] ]+ w[i][k]
动态转移方程
容易想到 背包中能塞入 第i组物品 的k倍,其不难超过当前规定的背包体积j,满足方程
v[i][k]<=j
显然 原 集合 dp[i][j] = max{dp[i-1][j],dp[i-1][j -v[i][k] ]+w[i][k] | v[i][k]<=j }
3.算法优化
3.1 滚动数组一维化
老生常谈了,因为转移方程只和上一层有关,所以可以滚动数组[自我滚动]优化
dp[j] = max{dp[j],dp[j -v[i][k] ]+w[i][k] | v[i][k]<=j }
注意: 要一维的必须循环逆序,和01背包一个道理,为什么完全背包不一样是正序呢,因为完全背包优化过后的动态转移方程 和当前i组有关,完全背包转移方程没优化也是需要逆序的.
3.2代码展示
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3+5;
int dp[N];
int v1[N],w1[N];
int main()
{
int n,V;
cin>>n>>V;
for(int i=1;i<=n;i++)
{
int s;
cin>>s;
for(int j=1;j<=s;j++)
{
cin>>v1[j]>>w1[j];
}
for(int j=V;j>0;j--)
{
for(int k=1;k<=s;k++)
{
if(j>=v1[k])
dp[j]=max(dp[j],dp[j-v1[k]]+w1[k]);
}
}
}
cout<<dp[V];
}