背包问题大合集

目录

目录

01 背包问题

题目

题面

【题目描述】

【输入】

【输出】

【输入样例】

【输出样例】

传送门

算法

二维数组

一维数组

完全背包问题

题目

题面

【题目描述】

【输入】

【输出】

【输入样例】

【输出样例】

传送门

算法

二维数组

一维数组

多重背包问题

题目

题面

【题目描述】

【输入】

【输出】

【输入样例】

【输出样例】

传送门

算法

二维数组

一维数组

尾声


01 背包问题

(其他大部分背包问题往往都可以转换成 01 背包问题进行求解)

题目

题面

【题目描述】

一个旅行者有一个最多能装 M 公斤的背包,现在有 n 件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn,求旅行者能获得最大总价值。

【输入】

第一行:两个整数,M(背包容量,M<=200)和N(物品数量,N<=30);

第2..N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。

【输出】

仅一行,一个数,表示最大总价值。

【输入样例】
10 4
2 1
3 3
4 5
7 9
【输出样例】

12

传送门

信息学奥赛一本通(C++版)在线评测系统http://ybt.ssoier.cn:8088/problem_show.php?pid=1267

算法

二维数组

可以用 f[i][v] 记录前 i 件物品,背包容量为 v 的最大价值,这样最后输出 f[n][m]

可以遍历所有物品,里面再枚举背包容量

可以发现,每个物品有两种选择:放或不放

这样状态转移方程为:f[i][v] = max(f[i - 1][v], f[i - 1][v - w[i]] + c[i])

这个方程的意思是:

选第 i 件物品放还是不放价值更大

其中,f[i - 1][v] 代表该物品不放,就是跟前 i - 1 件物品容量为 v 的最高价值一样

f[i - 1][v - w[i]] + c[i] 代表该物品放,因为该物品放,所以容量少了该物品的重量( - w[i]),而价值多了该物品的价值( + c[i])

最后取最大值就行了

注意,还要判断够不够放,即 w[i] <= v,否则,f[i][v] = f[i - 1][v](即只能不放)

总体思路有了,就可以上代码了

#include <iostream>
#include <algorithm>
#define N 205
using namespace std;

int m, n, w[N], c[N], f[N][N];

int main() {
	cin >> m >> n;
	for(int i = 1; i <= n; ++i)
		cin >> w[i] >> c[i];
	for(int i = 1; i <= n; ++i)
		for(int v = 1; v <= m; ++v)
			if(w[i] <= v)
				f[i][v] = max(f[i - 1][v], f[i - 1][v - w[i]] + c[i]);
			else
				f[i][v] = f[i - 1][v];
	cout << f[n][m] << endl;
	
	return 0;
}

提交一下哈

AC ! ! ! 

一维数组

这回我们来个降维打击呵

我们发现,每次用的都是 f[i - 1][*],所以似乎用一个一维数组就可以了,因为每次存的都是上一次的

但是这样就需要从后往前遍历了,因为每次都用 f[v - w[i]],是前面的,大家想一下,如果从前往后遍历,那么前面的是不是就已经被赋过值了,就不是以前的了

这样,状态转移方程为:f[v] = max(f[v], f[v - w[i]] + c[i])

所以这种方法虽然优化了空间,但是只要改一点就可以了

#include <iostream>
#include <algorithm>
#define N 205
using namespace std;

int m, n, w[N], c[N], f[N];

int main() {
	cin >> m >> n;
	for(int i = 1; i <= n; ++i)
		cin >> w[i] >> c[i];
	for(int i = 1; i <= n; ++i)
		for(int v = m; v > 0; --v)
			if(w[i] <= v)
				f[v] = max(f[v], f[v - w[i]] + c[i]);
	cout << f[m] << endl;
	
	return 0;
}

那么稍微的提交一下~

再次 AC ! ! !

完全背包问题

题目

题面

【题目描述】

设有n种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为M,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于M,而价值的和为最大。

【输入】

第一行:两个整数,M(背包容量,M≤200)和N(物品数量,N≤30);

第2..N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。

【输出】

仅一行,一个数,表示最大总价值。

【输入样例】
10 4
2 1
3 3
4 5
7 9
【输出样例】
max=12

传送门

信息学奥赛一本通(C++版)在线评测系统http://ybt.ssoier.cn:8088/problem_show.php?pid=1268

算法

本题目的二维数组解法、一维数组解法都和 01 背包的类似,都只是稍加改动

二维数组

01 背包问题只能放一个,而这道题可以放无限个,所以可以再在里面加一层循环,枚举该物品放  t  个,然后重量和价钱都乘以 t(即 f[i][v] = max(f[i - 1][v], f[i - 1][v - t * w[i]] + t * c[i]),注意判断也需要乘以 t:if(w[i] * t <= v),否则 break 掉)

代码如下:

#include <iostream>
#define N 205
using namespace std;

int m, n, w[N], c[N], f[N];

int main() {
	cin >> m >> n;
	for(int i = 1; i <= n; ++i)
		cin >> w[i] >> c[i];
	
	for(int i = 1; i <= n; ++i)
		for(int v = m; v > 0; --v)
			for(int t = 1; ; ++t)
				if(w[i] * t <= v)
					f[v] = max(f[v], f[v - t * w[i]] + t * c[i]);
				else
					break;
	
	cout << "max=" << f[m] << endl;
	
	return 0;
}

提交一下

AC ! ! ! 

一维数组

首先要想一下 01 背包的一维数组解法为什么是从大到小,因为他放过的不能再放了

而这个放过的可以再放,所以只要更改一下循环次序就可以了(* ̄︶ ̄)

代码如下:

#include <iostream>
#include <algorithm>
#define N 205
using namespace std;

int m, n, w[N], c[N], f[N];

int main() {
	cin >> m >> n;
	for(int i = 1; i <= n; ++i)
		cin >> w[i] >> c[i];
	for(int i = 1; i <= n; ++i)
		for(int v = 1; v <= m; ++v)
			if(w[i] <= v)
				f[v] = max(f[v], f[v - w[i]] + c[i]);
	cout << "max=" << f[m] << endl;
	
	return 0;
}

提交一下哈~

AC too ! ! ! 

(一个简单有效的优化:如果一件物品的重量比另一件的物品大,而这件物品的价值又没有另一件大,直接去掉该物品即可,正确性显然)

多重背包问题

题目

题面

【题目描述】

为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。

【输入】

第一行二个数n(n≤500),m(m≤6000),其中n代表希望购买的奖品的种数,m表示拨款金额。

接下来n行,每行3个数,v、w、s,分别表示第I种奖品的价格、价值(价格与价值是不同的概念)和能购买的最大数量(买0件到s件均可),其中v≤100,w≤1000,s≤10。

【输出】

一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。

【输入样例】
5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1
【输出样例】
1040

传送门

信息学奥赛一本通(C++版)在线评测系统http://ybt.ssoier.cn:8088/problem_show.php?pid=1269

算法

二维数组

几乎跟完全背包的二维一模一样!

完全背包的二维是没有限制,啥时候放不下了就不放了,换一个容量(for(int t = 1;_; ++t))

这次呢,有限制,直接从 0 到 s[i],其他都不用变

for(int k = 0; k <= s[i]; ++k)

总体代码:

#include <iostream>
#include <algorithm>
#define N 6005
using namespace std;

int n, m, v[N], w[N], s[N], f[N];

int main() {
	cin >> n >> m;
	for(int i = 1; i <= n; ++i)
		cin >> v[i] >> w[i] >> s[i];
	for(int i = 1; i <= n; ++i)
		for(int j = m; j >= 0; --j)
			for(int k = 0; k <= s[i]; ++k)
				if(j >= k * v[i])
					f[j] = max(f[j], f[j - k * v[i]] + k * w[i]);
				else
					break;
	cout << f[m] << endl;
	
	return 0;
}

 提交一下!

AC ! ! !

一维数组

emmmm······

有一个特别简单的转换成 01 背包的方法,就是把每件物品都克隆出来对应的几件(有几件就搞几件),这样不就行了吗?

但是有点费空间……

这时,我们发现二进制可以凑成任何数(最多到他们加起来),比如 1, 2, 4, 8 最大可以组成 1 + 2 + 4 + 8 = 15:

1 = 1

2 = 2

3 = 2 + 1

4 = 4

5 = 4 + 1

6 = 4 + 2

7 = 4 + 2 + 1

8 = 8

9 = 8 + 1

10 = 8 + 2

11 = 8 + 2 + 1

12 = 8 + 4

13 = 8 + 4 + 1

14 = 8 + 4 + 2

15 = 8 + 4 + 2 + 1

这样,我们可以把每件物品变成二进制数,即价格和价值都乘以该二进制数

但是就比如可以放 13 件,如果用 1, 2, 4 最可以组成 7 件,少了;那如果用 1, 2, 4, 8 最多可以组成 15 件,又多了,都不符合题意:能放 13 件,所以,让他们的和等于 13 只能用 1, 2, 4, 6

1 = 1

2 = 2

3 = 1 + 2

4 = 4

5 = 4 + 1

6 = 6

7 = 6 + 1

8 = 6 + 2

9 = 6 + 2 + 1

10 = 6 + 4

11 = 6 + 4 + 1

12 = 6 + 4 + 2

13 = 6 + 4 + 2 + 1

即必须让他们的和等于能放几件就行了:一个数最开始等于 1,然后只要还小于等于能放的个数,就新添一个价值、价格都乘以该数的物品,然后个数减该数,该数乘以 2 

别忘了最后个数还剩下呢,再新添一个都乘以个数的物品就行了

这样就完成了克隆的过程:

for(int i = 1; i <= n; ++i) {
	cin >> x >> y >> s;
	t = 1;
	while(t <= s) {
		v[++n1] = t * x;
		w[n1] = t * y;
		s -= t;
		t *= 2;
	}
	v[++n1] = s * x;
	w[n1] = s * y;
}

有同学问了:万一不剩下了呢?那不就都是 0 了吗?

好,能想出来这个问题的同学很棒,一看就自己思考了

我们想一下:当我们最后当 01 背包的时候,这件物品的 f[j] 和 f[j - v[i]] + w[i] 就都一样了,都是不放的状态,所以这点不用考虑

(已经转换成 01 背包了,那么 01 背包就不用说了)

下面是整体代码:

#include <iostream>
#include <algorithm>
#define N 10005
using namespace std;

int v[N], w[N], f[N], n, m, x, y, s, t, n1;

int main() {
	cin >> n >> m;
	for(int i = 1; i <= n; ++i) {
		cin >> x >> y >> s;
		t = 1;
		while(t <= s) {
			v[++n1] = t * x;
			w[n1] = t * y;
			s -= t;
			t *= 2;
		}
		v[++n1] = s * x;
		w[n1] = s * y;
	}
	for(int i = 1; i <= n1; ++i)
		for(int j = m; j >= v[i]; --j)
			f[j] = max(f[j], f[j - v[i]] + w[i]);
	cout << f[m] << endl;
	
	return 0;
}

提交一下!

AC ! ! !

尾声

如果这篇超长博客对您(您的团队)有帮助的话,就帮忙点个赞,加个关注!

最后,祝您(您的团队)在 OI 的路上一路顺风!!!

┬┴┬┴┤・ω・)ノ Bye~Bye~

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值