分组背包问题的3种形式:每组最多选1个(模板),必须选1个,至少选1个
每组最多选1个
有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。接下来有 N 组数据:每组数据第一行有一个整数
S
i
S_i
Si,表示第 i 个物品组的物品数量;每组数据接下来有
S
i
S_i
Si 行,每行有两个整数
v
i
j
,
w
i
j
v_{ij},w_{ij}
vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围
0
<
N
,
V
≤
100
0<N,V≤100
0<N,V≤100,
0
<
S
i
≤
100
0<S_i≤100
0<Si≤100,
0
<
v
i
j
,
w
i
j
≤
100
0<v_{ij}, w_{ij}≤100
0<vij,wij≤100,
代码
没有优化一维数组
#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],只有一维数组就不需要继承上一次的状态了
#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个
小红希望出一场题目,但是他的实力又不够,所以他想到可以从以前的比赛中各抽一题,来组成一场比赛。不过一场比赛的难度应该是有限制的,所以所以这一场比赛会给一个目标难度分数 t a r g e t target target。
小红选 n n n 场比赛,每场 m m m 个题,小红会从每一场选一道题,使其组成的题的难度分数尽量接近 t a r g e t target target。小红想知道挑选的题的难度分数与 t a r g e t target target 相差的最小值是多少。
输入描述:
第一行输入两个整数
n
,
m
(
1
≤
n
≤
100
,
1
≤
m
≤
20
)
n,m (1≤n≤100,1≤m≤20)
n,m(1≤n≤100,1≤m≤20)代表小红选了
n
n
n 场比赛,每场比赛有
m
m
m 道题。
此后
n
n
n 行,第
i
i
i 行输入
m
m
m 个整数
a
i
,
j
(
1
≤
a
i
,
j
≤
50
)
a_{i,j} (1≤a_{i,j}≤50)
ai,j(1≤ai,j≤50)代表第
i
i
i 场比赛的第
j
j
j 道题的难度分数。
最后一行输入一个整数 $target (1≤target≤5000) $代表小红希望这一场比赛的难度分数。
输出描述:
在一行上输出一个整数,表示挑选的题的难度分数与 t a r g e t target target 相差的最小值。
示例1
输入
3 3
1 4 7
2 5 8
3 6 9
10
输出
1
说明
小红可以选第一场比赛的第一道题,第二场比赛的第一道题,第三场比赛的第二道题,这样挑选的题的难度分数为 1+2+6=9,与target 相差的最小值为 1。
代码
#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个
注意循环嵌套顺序变了
#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;
}