分组背包变体

分组背包问题的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,V100,
0 < S i ≤ 100 0<S_i≤100 0<Si100,
0 < v i j , w i j ≤ 100 0<v_{ij}, w_{ij}≤100 0<vij,wij100,

代码

没有优化一维数组

#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(1n100,1m20)代表小红选了 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(1ai,j50)代表第 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个

hdu3033

注意循环嵌套顺序变了

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我的Doraemon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值