分组背包问题的3种形式:每组最多选1个(模板),必须选1个,至少选1个

每组最多选1个

有 N 组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

输入格式

第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。接下来有 N 组数据:每组数据第一行有一个整数 $Si$,表示第 i 个物品组的物品数量;每组数据接下来有 $S_i$ 行,每行有两个整数 $v{ij},w_{ij}$,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式

输出一个整数,表示最大价值。

数据范围

$0<N,V≤100$,
$0<Si≤100$,
$0<v
{ij}, w_{ij}≤100$,

代码

没有优化一维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1010;
int n,m;
int v[N][N],w[N][N],s[N];
int f[N][N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>s[i];
for(int j=0;j<s[i];j++)
{
cin>>v[i][j]>>w[i][j]; //读入
}
}

for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
f[i][j]=f[i-1][j]; //不选
for(int k=0;k<s[i];k++)
{
if(j>=v[i][k]) f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
}
}
}
cout<<f[n][m]<<endl;
}

用逆序更新优化一维数组,由于可以不选,才可以优化掉一维数组,f[i][j]=f[i-1][j],只有一维数组就不需要继承上一次的状态了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1010;
int n,m;
int v[N][N],w[N][N],s[N];
int f[N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>s[i];
for(int j=0;j<s[i];j++)
{
cin>>v[i][j]>>w[i][j];
}
}
for(int i=1;i<=n;i++)
for(int j=m;j>=0;j--)
for(int k=0;k<=s[i];k++)
if(j>=v[i][k]) f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);


cout<<f[m]<<endl;
}

每组必须选1个

小红希望出一场题目,但是他的实力又不够,所以他想到可以从以前的比赛中各抽一题,来组成一场比赛。不过一场比赛的难度应该是有限制的,所以所以这一场比赛会给一个目标难度分数 $target$。

小红选 $n$ 场比赛,每场 $m$ 个题,小红会从每一场选一道题,使其组成的题的难度分数尽量接近 $target$。小红想知道挑选的题的难度分数与 $target$ 相差的最小值是多少。

输入描述:

第一行输入两个整数 $n,m (1≤n≤100,1≤m≤20)$代表小红选了 $n$ 场比赛,每场比赛有 $m$ 道题。
此后 $n$ 行,第 $i$ 行输入 $m$ 个整数 $a{i,j} (1≤a{i,j}≤50)$代表第 $i$ 场比赛的第 $j$ 道题的难度分数。
最后一行输入一个整数 $target (1≤target≤5000) $代表小红希望这一场比赛的难度分数。

输出描述:

在一行上输出一个整数,表示挑选的题的难度分数与 $target$ 相差的最小值。

示例1

输入

1
2
3
4
5
3 3
1 4 7
2 5 8
3 6 9
10

输出

1
1

说明

1
小红可以选第一场比赛的第一道题,第二场比赛的第一道题,第三场比赛的第二道题,这样挑选的题的难度分数为 1+2+6=9,与target 相差的最小值为 1。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int n, m, target;
int a[105][25];
bool f[2][5005]; // f[i][j]表示前i场比赛能否凑出总分为j的题目

signed main() {
cin >> n >> m;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> a[i][j];
}
}
f[0][0] = true;
for (int i = 0; i < n; i++) {
memset(f[(i+1)%2], false, sizeof f[(i+1)%2]);
for (int j = 0; j <= 5000; j++) {
for (int k = 0; k < m; k++) {
if (j - a[i][k] >= 0) {
f[(i+1)%2][j] |= f[i%2][j-a[i][k]];
if (f[(i+1)%2][j]) {
break;
}
}
}
}
}
cin >> target;
int ans = INT_MAX;
for (int j = 0; j <= 5000; j++) {
if (f[n%2][j]) ans = min(ans, abs(target - j));
}
cout << ans << endl;
return 0;
}
/*
2 2
3 6
3 6
8
2
*/

每组至少选1个

hdu3033

注意循环嵌套顺序变了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int maxn = 105;
int num[11],cost[11][maxn],value[11][maxn];
int n,m,k,dp[11][10005];

int main() {
int a,b,c;
while(scanf("%d%d%d",&n,&m,&k)!=EOF) {
memset(num,0,sizeof(num));
memset(dp,-1,sizeof(dp));
for(int i = 1; i <= n; i++) {
scanf("%d%d%d",&a,&b,&c);
num[a]++;
cost[a][num[a]] = b;
value[a][num[a]] = c;
}
memset(dp[0],0,sizeof(dp[0]));
for(int i = 1; i <= k; i++) {
for(int j = 1; j <= num[i]; j++) {
for(int v = m; v >= cost[i][j]; v--) {
if(dp[i][v-cost[i][j]] != -1) //从本组里面取,之前可能抽取了多个
dp[i][v] = max(dp[i][v],dp[i][v-cost[i][j]]+value[i][j]);
if(dp[i-1][v-cost[i][j]] != -1) //从本组中抽取的第一个
dp[i][v] = max(dp[i][v],dp[i-1][v-cost[i][j]]+value[i][j]);
/*
顺序不能调转,因为如果代价为0,调转的话,有可能出现先有dp[i][v] = dp[i-1][v-0]+w,再有
dp[i][v] = dp[i][v-0]+w = dp[i-1][v-0]+w+w,所以物品取了两次
*/
}
}
}
if(dp[k][m] == -1) printf("Impossible\n");
else printf("%d\n",dp[k][m]);
}
return 0;
}