分组背包模型讲解和【题解】【洛谷P1757】——通天之分组背包
1.概念解析
分组背包是背包问题的一种变体,其核心在于将物品分成若干组,每组最多只能选择一个物品。这种设定使得分组背包问题在处理上有其独特之处
。
2.举例了解
通天之分组背包
题目背景
直达通天路·小 A 历险记第二篇
题目描述
自 01 01 01 背包问世之后,小 A 对此深感兴趣。一天,小 A 去远游,却发现他的背包不同于 01 01 01 背包,他的物品大致可分为 k k k 组,每组中的物品相互冲突,现在,他想知道最大的利用价值是多少。
输入格式
两个数 m , n m,n m,n,表示一共有 n n n 件物品,总重量为 m m m。
接下来 n n n 行,每行 3 3 3 个数 a i , b i , c i a_i,b_i,c_i ai,bi,ci,表示物品的重量,利用价值,所属组数。
输出格式
一个数,最大的利用价值。
输入输出样例
输入 #1
45 3
10 10 1
10 5 1
50 400 2
输出 #1
10
提示
0
≤
m
≤
1000
0 \leq m \leq 1000
0≤m≤1000,
1
≤
n
≤
1000
1 \leq n \leq 1000
1≤n≤1000,
1
≤
k
≤
100
1\leq k\leq 100
1≤k≤100,
a
i
,
b
i
,
c
i
a_i, b_i, c_i
ai,bi,ci 在 int
范围内。
思路解析
建议先食用01背包问题模型讲解和【题解】—— [NOIP2005 普及组] 采药
1.状态:
dp[i][j]
表示背包容量为j
只选前i
组物品的最大价值。
根据问题的定义来定义就行了。
2.初始条件:
dp[i][j] = dp[i - 1][j];
当增加一个物品的选项的时候,至少前面的dp[i-1][j]
的取法是合法的,可以直接采用。
3.状态转移方程: d p [ i ] [ j ] = m a x ( d p [ j − a [ i ] [ k ] . w e i g h t ] + a [ i ] [ k ] . v a l u e ) dp[i][j]=max(dp[j-a[i][k].weight]+a[i][k].value) dp[i][j]=max(dp[j−a[i][k].weight]+a[i][k].value)
只要你看懂了 01 01 01背包问题,那理解这个状态转移方程应该也不难。
i
枚举的是每一组,j
枚举的是重量,k
枚举的是第i
组的每一件物品。在编写代码中,我们使用一个数组num
储存每一组的物品个数。使用a[i][j]
储存第i
组的第j
个物品的信息。
4.答案:
dp[cnt][m]
,其中cnt
为组数,m
为背包容量。
5.时间复杂度: O ( n m ) O(nm) O(nm)
对于时间复杂度,由于在循环中枚举了每一组的每一件物品,就相当于枚举了所有物品
。总共循环
n
n
n次。
注意:通过观察,此题中的组号为1~k
(不一定会达到
k
k
k),可以直接找出最大的编号,即为组数。在实际使用中可以进行略微改动。
3.示例代码
#include<bits/stdc++.h>
using namespace std;
#define MAXM 1010//m的数据范围,可根据题目修改
#define MAXN 1010//n的数据范围(也作最大组数),可根据题目修改
//用n储存物品件数,m储存背包重量
//用dp[i][j]背包容量为j只选前i组物品的最大价值,num[i]表示第i组的物品个数
int n, m, dp[MAXN][MAXM], num[MAXN], cnt;//cnt储存物品组数
struct object{// 储存物品的重量和价值
int weight, value;
}a[MAXN][MAXN];//a[i][j]表示第i组的第j件物品
int main(){
scanf("%d%d", &m, &n);
for (int i = 1; i <= n; i++){
int w, v, l;
scanf("%d%d%d", &w, &v, &l);
cnt = max(cnt, l);//更新组数
a[l][++num[l]].weight = w;//储存当前物品的重量和大小
a[l][num[l]].value = v;
}
for (int i = 1; i <= cnt; i++)//枚举组数
for (int j = 1; j <= m; j++){//枚举重量
dp[i][j] = dp[i - 1][j];//先继承上一层的状态
for (int k = 1; k <= num[i]; k++)//枚举每一组的物品
if (a[i][k].weight <= j)//符合条件,状态转移
dp[i][j] = max(dp[i][j], dp[i - 1][j - a[i][k].weight] + a[i][k].value);
}
printf("%d\n", dp[cnt][m]);//注意输出
return 0;
}
4.滚动数组优化
既然 01 01 01背包问题可以使用滚动数组优化,那分组背包行不行呢?当然是可以的,如下:
#include<bits/stdc++.h>
using namespace std;
#define MAXM 1010//m的数据范围,可根据题目修改
#define MAXN 1010//n的数据范围(也作最大组数),可根据题目修改
//用n储存物品件数,m储存背包重量
//用dp[i][j]背包容量为j只选前i组物品的最大价值,num[i]表示第i组的物品个数
int n, m, dp[MAXM], num[MAXN], cnt;//cnt储存物品组数
struct object{//储存每件物品的重量和价值
int weight, value;
}a[MAXN][MAXN];//a[i][j]表示第i组的第j件物品
int main(){
scanf("%d%d", &m, &n);
for (int i = 1; i <= n; i++){
int w, v, l;
scanf("%d%d%d", &w, &v, &l);
cnt = max(cnt, l);//更新组数
a[l][++num[l]].weight = w;//储存当前物品的重量和大小
a[l][num[l]].value = v;
}
for (int i = 1; i <= cnt; i++)//枚举组数
for (int j = m; j >= 0; j--)//枚举重量
for (int k = 1; k <= num[i]; k++)//枚举每一组的物品
if (a[i][k].weight <= j)//符合条件,状态转移
dp[j] = max(dp[j], dp[j - a[i][k].weight] + a[i][k].value);
printf("%d\n", dp[m]);//注意输出
return 0;
}
喜欢就订阅此专辑吧
欢迎关注蓝胖子编程教育