GDUT ACM2022寒假集训 专题二 D E(01背包)

更好的阅读体验请前往:Paxton的小破站

一、01背包

01背包是在M件物品取出若干件放在空间为W的背包里,每件物品的体积为W1,W2至Wn,与之相对应的价值为P1,P2至Pn。01背包是背包问题中最简单的问题。01背包的约束条件是给定几种物品,每种物品有且只有一个,并且有权值和体积两个属性。——百度百科

01背包相关的问题有三个注意点,1、物品重量 2、物品价值 3、背包容量

每一件物品都有取和不取两种情况,同时还要依据题目限制判断物品能不能取,且每个物品都只有一件,求出将哪些物品装入背包可使价值总和最大

这样的问题最简单的做法就是搜索尝试各种可能的物品组合,并找出价值最高的组合。

但是显然很容易超时,这种算法的时间复杂度是O(2ⁿ)

所以我们应当使用动态规划法,动态规划的基本特征是1、最优子结构 2、无后效性 3、重复子问题

也就是问题的最优解包含子问题的最优解,求解问题的时候直接使用子问题的最优解

举个例子:
物品编号: 1 2 3 4
物品重量: 2 3 4 5
物品价值: 3 4 5 8

设dp(x,y)为当背包当前容量为y时,可以取前x件物品时的价值最优解
假设背包总容量为8
Hwcf6s.md.jpg
则对于dp(4,8)背包容量为8有前四个物品可选,有两种情况,取第四件物品与不取第四件物品

若取,则变为子问题dp(3,3)+8,背包容量为3,有前3个物品可选
因为取了第四件物品,则需在子问题最优解dp(3,3)的基础上加上第四件物品的价值8

若不取,则变为子问题dp(3,8),背包容量为8,有前3个物品可选

按照这个规律进行完整推导,可得问题最优解为12
Hw2gMj.md.jpg

01背包的状态转移方程为
HwRxns.md.png

max中前者为不取的情况,后者为取的情况

在代码实现中我们需要使用两层循环将背包容量从0到满时每一种物品的取与不取的情况全部求出即下图dp(4,8)=12

Hwf8iV.md.png

二、例题一(采药)

原题链接:https://vjudge.net/contest/477404#problem/D

1、题干

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是辰辰,你能完成这个任务吗?

2、输入格式

第一行有 22 个整数 T(1≤T≤1000)和 M(1≤M≤100),用一个空格隔开,T 代表总共能够用来采药的时间,M 代表山洞里的草药的数目。

接下来的 M 行每行包括两个在 1 到 100 之间(包括 1 和 100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

3、输出格式

输出在规定的时间内可以采到的草药的最大总价值。

4、样例

sample input

70 3
71 100
69 1
1 2

sample output

3

5、数据范围

对于 30% 的数据,M≤10;
对于全部的数据,M≤100。

例题一题解

1、分析

本题为十分清晰的01背包模板题
草药价值-物品价值 采药时间-物品重量 能够用来采药的时间-背包大小

2、代码

#include <iostream>
using namespace std;
int t, m;
int ti[105], val[105];
int dp[105][105];
int main()
{
	cin >> t >> m;//t背包大小  m物品数量
	for (int i = 1; i <= m; ++i)
	{
		cin >> ti[i] >> val[i];//ti为物品重量   val为物品价值
	}
	for (int i = 1; i <= m; ++i)//外层循环由少到多遍历可取前i件物品的情况
	{
		for (int j = t; j >= 0; --j)//内层循环遍历所有背包大小情况
		{
			if (ti[i] <= j)//物品能够取得
			{
				dp[i][j] = max(dp[i - 1][j - ti[i]] + val[i], dp[i - 1][j]);
			}//当前最优解为 取与不取两种情况最优解的最大值
			else
			{
				dp[i][j] = dp[i - 1][j];//不能取第i件物品则最优解为上一件物品的最优解
			}
		}
	}
	cout << dp[m][t];
	return 0;
}

二、例题二(CD UVA624)

原题链接:https://vjudge.net/contest/477404#problem/E

1、题干

你正在一个漫长的行车之旅中,你有一个磁带机,但是你最喜欢的音乐在CD(音轨)上。 你需要把他们放在磁带机上。你有一个可以存下N分钟长的磁带, 磁带里有若干音轨,你需要选择一个最优方案使得未用空间最小

注意
一片CD里没有超过20个音轨
没有音轨比N分钟长
音轨互不重复
每一个音轨的长度是一个int范围的数
N同时也是一个int 你的程序需要找到一个最优方案(包含若干音轨),并且按照原输入顺序输出

2、输入格式

每一行有一个N表示磁带长度,T表示音轨数量,以及其他音轨的时长

3、输出格式

一套磁带里放的音轨选择 和他们时间的总和

4、样例

sample input

5 3 1 3 4
10 4 9 8 4 2
20 4 10 5 7 4
90 8 10 23 1 2 3 4 5 7
45 8 4 10 44 43 12 9 8 2

sample output

1 4 sum:5
8 2 sum:10
10 5 4 sum:19
10 23 1 2 3 4 5 7 sum:55
4 10 12 9 8 2 sum:45

例题二题解

1、分析

本题是在基本的01背包的基础上增加了选择物品的显示
并且音轨的长度同时代表了物品的重量与价值

磁带长度——背包大小
音轨长度——物品重量
音轨长度——物品价值

关于物品的显示,我们先把所有情况的最优解的表格展示出来
HwIXcT.png
已知最右下角的数字一定是本问题的最优解,也就是这个解一定会被用到
我们需要从这个解开始往回推,找出子问题的最优解

状态转移方程中dp[i][j] = max(dp[i - 1][j - p[i]] + p[i], dp[i - 1][j]);

可知若该物品被取得,max将会返回前者,则dp[i][j]-p[i]一定会与dp[i - 1][j - p[i]]的数值相同,即当这两者的数值相同的时候,该物品被取得

		int t = n, k = 0;//t表示背包容量  k计数
		for (int i = z; i >= 1; --i)//从问题最优解开始往回查找,故由z开始循环
		{
			if (dp[i][t] - p[i] == dp[i - 1][t - p[i]])
			{//由状态转移方程推得该判断条件,两者相同,该物品被取得
				path[++k] = p[i];//记录该件物品重量
				t -= p[i];//取得物品,背包容量减少
			}
		}

2、代码

#include <bits/stdc++.h>
using namespace std;
int dp[25][999999], p[25], path[25];
int n, z;

int main()
{
	while (scanf("%d", &n) != EOF) // n表示背包容量
	{
		memset(dp, 0, sizeof(dp));
		memset(path, 0, sizeof(path));
		memset(p, 0, sizeof(p));//每次读入一组数据都要清空,防止上一组数据影响
		scanf("%d", &z); // z表示物品个数
		for (int i = 1; i <= z; ++i)
		{
			cin >> p[i];//p同时表示物品的重量与价值
		}
		for (int i = 1; i <= z; ++i) //从前往后遍历一次可取前i个物品的情况
		{
			for (int j = n; j >= 0; j--)//所有可能的背包容量情况
			{
				if (j >= p[i])//物品可取
				{
					dp[i][j] = max(dp[i - 1][j - p[i]] + p[i], dp[i - 1][j]);
				}
				else//不可取
				{
					dp[i][j] = dp[i - 1][j];
				}
			}
		}
		//______________物品显示______________
		int t = n, k = 0;//t表示背包容量  k计数
		for (int i = z; i >= 1; --i)//从问题最优解开始往回查找,故由z开始循环
		{
			if (dp[i][t] - p[i] == dp[i - 1][t - p[i]])
			{//由状态转移方程推得该判断条件
				path[++k] = p[i];//记录该件物品重量
				t -= p[i];//取得物品,背包容量减少
			}
		}

		for (int i = k; i >= 1; --i)
		{//反向输出物品
			cout << path[i] << " ";
		}
		//————————————————————————————————————
		cout << "sum:" << dp[z][n] << endl;
	}
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值