背包问题_(DP经典),二(多重背包)

背包系列已更新

一,(01背包,填满背包)

二,(多重背包)

三,(完全背包)

 

目录

一,每种物品有k个的多重背包

方法一,一种朴素的想法,其实k个分出来不就是01背包吗,那我们写的时候再一个循环遍历k个就好了

AC代码

方法二,其实,每个数都可以由二进制数表示,那么我们可以用二进制优化k,就不用去遍历k遍

二,多种物品分成k组,每组只能取一种的多重背包(也叫分组背包)

AC代码


 

多重背包有俩种,一种是每种物品有k个,一种是多种物品分成k组,每组只能取一种

一,每种物品有k个的多重背包

 

方法一,一种朴素的想法,其实k个分出来不就是01背包吗,那我们写的时候再一个循环遍历k个就好了

 927588f0b8784c59a7bb0994318708ff.png

Sample Input

1
8 2
2 100 4
4 100 2

Sample Output

400

相信背包思想你以熟悉,接下来我们都用一维压缩来写

AC代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>

using namespace std;
#define ll long long

const int N = 110;

int s[N];//存放每种物品有几个,s[i]表示第i种物品有s[i]个
int dp[N];
int p[N];//价格(即背包空间
int w[N];//重量(即价值

int main() {
	int c;
	cin >> c;
	while (c--) {
		memset(dp, 0, sizeof(dp));//dp切记要每次都初始化
		int n, m;
		cin >> n >> m;
		for (int i = 1; i <= m; ++i)cin >> p[i] >> w[i] >> s[i];
		                       //j逆序还是防止重复放入,一维压缩要注意                       //k注意条件,首先当然是k不大于s[i]个,然后是j-k * p[i]不小于0,不然空间负数会出意外
		for (int i = 1; i <= m; ++i)for (int j = n; j >= p[i]; --j)for (int k = 0; k <= s[i] && k * p[i] <= j; ++k) {//跟01背包写法几乎一致,但是多了个k个的循环
			dp[j] = max(dp[j], dp[j - k * p[i]] + k * w[i]);
		}
		cout << dp[n]<<endl;

	}

	return 0;
}

方法二,其实,每个数都可以由二进制数表示,那么我们可以用二进制优化k,就不用去遍历k遍

如70可以表示为32+16+8+4+2+1+7,这几个数可以组成1--63任意数(看成二进制就很清楚)

那么我们把有63个的物品分成上面6种数值的物品,无论最优是哪个数字,我都能通过DP得到

这样时间复杂度从n^3变成(n^2)*logs(二进制是指数级降次,从n压缩为log)

//注意,习惯开小数组的,因为二进制优化,这里可能N小空间存不下(会把一个分成很多个),数组开大一点

#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>

using namespace std;
#define ll long long

const int N = 110;

int dp[N];
int p[N*N];//价格(即背包空间          //因为二进制优化,这里可能N小空间存不下(会把一个分成很多个),数组开大一点
int w[N*N];//重量(即价值

int main() {
	int c;
	cin >> c;
	while (c--) {
		memset(dp, 0, sizeof(dp));//dp切记要每次都初始化
		int n, m;
		cin >> n >> m;
		int a,b,s;//a,b,s暂时存放数据
		int ans = 0;//表示分开后物品有几个
		for (int i = 1; i <= m; ++i) {
			cin >> a >> b >> s;
			int k = 1;
			while (k <= s) {
				ans++;
				s -= k;//拿走k份,c自然要减,不然最后可以表示的最大数肯定不止原来的c了
				p[ans] = a*k; w[ans] = b*k;//*k表示重新组成后的物品的价格与重量
				k <<= 1;//二进制左移,等于*2
			}
			if (s > 0) {  //如果c有剩,剩下的存为一组
				ans++;
				p[ans] = a * s; w[ans] = b * s;
			}


		}             //最后分完有ans个物品,已经变成01背包了
		for (int i = 1; i <= ans; ++i)
			for (int j = n; j >= p[i]; --j)
			dp[j] = max(dp[j], dp[j - p[i]] + w[i]);
		cout << dp[n]<<endl;

	}

	return 0;
}

二,多种物品分成k组,每组只能取一种的多重背包(也叫分组背包)

其实基础还是01背包,然后多了一个循环来遍历每一组的物品

洛谷p1757

d01dade955e9417a8cf483298f10db9f.png

 

我们只需要每次对第i组遍历j空间下存入这一组哪个最优,全部填表,最后自然在dp转移中最优

AC代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>

using namespace std;
#define ll long long

const int N = 1100;

int dp[N];
int zu[N];//zu[i]代表第i组里面的物品数量
int w[N][N];//w[i][k]表示第i组第k个物品的重量
int p[N][N];//p则表示......的价值
int main() {
	int  n, m;
	cin >> m >> n;
	
	int a, b, c;
	int ans = 0;
	for (int i = 1; i <= n; ++i) {
		cin >> a >> b >> c;
		ans = max(ans, c);//记录有几组,方便后面dp
		zu[c]++;
		w[c][zu[c]] = a;//zu[c]表示是这一组第几个物品
		p[c][zu[c]] = b;
	}                                //j还是从大到小,还是防止重复存入,这里因为物品信息在内循环,所以j临界到0,但是循环内部就要记得判断了
	for (int i = 1; i <= ans; ++i)for (int j = m; j >= 0; --j)for (int k = 1; k <= zu[i]; ++k) {
		if (j - w[i][k] >= 0)dp[j] = max(dp[j], dp[j - w[i][k]] + p[i][k]);
	}
	cout << dp[m] << endl;
	return 0;
}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值