CCF-202209-2-何以包邮?01背包

目录

1、题目描述:

2、思路1:动态规划

2.1、确定dp数组及下标含义

2.2、递推公式

2.3、初始化dp数组

2.4、确定遍历顺序

2.5、C++实现如下

3、思路2:暴力法-空间换时间


1、题目描述:

新学期伊始,适逢顿顿书城有购书满  x 元包邮的活动,小 P 同学欣然前往准备买些参考书。
一番浏览后,小 P 初步筛选出 n 本书加入购物车中,其中第 i 本(1≤ i ≤ n)的价格为 a[i] 元。
考虑到预算有限,在最终付款前小 P 决定再从购物车中删去几本书(也可以不删),使得剩余图书的价格总和 sum 在满足包邮条件(sum ≥ x)的前提下最小。

试帮助小 P 计算,最终选购哪些书可以在凑够 x 元包邮的前提下花费最小?

输入格式:

从标准输入读入数据。

输入的第一行包含空格分隔的两个正整数 n 和 x ,分别表示购物车中图书数量和包邮条件。

接下来输入 n 行,其中第 i 行(1≤ i ≤ n)仅包含一个正整数 a[i],表示购物车中第 i 本书的价格。输入数据保证 n 本书的价格总和不小于 x 。

输出格式:

输出到标准输出。

仅输出一个正整数,表示在满足包邮条件下的最小花费。

示例:

样例1输入

4 100
20
90
60
60

样例1输出

110

样例1解释

购买前两本书(20+90)即可包邮且花费最小。

样例2输入

3 30
15
40
30

样例2输出

30

样例2解释

仅购买第三本书恰好可以满足包邮条件。

样例3输入

2 90
50
50

样例3输出

100

样例3解释

必须全部购买才能包邮。

2、思路1:动态规划

阅读题目可以发现,题目要求选择满足包邮条件下的最小花费,听起来很像01背包相关的问题,与常规的01背包问题不同的是,背包的大小似乎是变化的,以往做的题目都是确定 一个条件选择满足的最大值,但该题目要求选择的是满足条件下的最小的值,所以第一次做这个题目的时候只得了95分,最后还是超时了五分,因为我将背包大小设置为x,先判断背包大小为x时能否装满价值等于x的书,如果不行就逐个增加背包的大小再重新判断,所以需要执行很多次循环,也就超时了。

题目比较巧妙的地方是,他将条件进行了转换,如果我们反着思考,背包的大小为sum-x,当背包大小为sum-x时能装的最大价值为ex,而ex这个数值一定是小于等于x的,因为此题目中,书籍的质量与价值相等,所以sum-ex即为满足包邮条件的最小花费,理清了思路代码实现非常简单,即经典的01背包问题。

2.1、确定dp数组及下标含义

dp[i]表示大小为i的背包能放最大价值为dp[i]的书

2.2、递推公式

dp[j] = max(dp[j],dp[j-a[i]]+a[i]);

2.3、初始化dp数组

dp[0]表示背包大小为0时能放下多少书,根据题目描述,dp[0]自然是0,而其他的dp数组初始化为最小非负数即可。

2.4、确定遍历顺序

外层遍历物品从前往后,内层遍历背包从大到小

2.5、C++实现如下

#include<iostream>
#include<algorithm>
using namespace std;

int main() {
	int n,x;
	cin>>n>>x;
	int a[n];
	int sum = 0;
	for(int i = 0; i<n; ++i) {
		cin>>a[i];
		sum+=a[i];
	}

	int target = sum-x;
	vector<int> dp(target+1,0);
	for(int i = 0; i<n; ++i) {
		for(int j = target; j>=a[i]; --j) {
			dp[j] = max(dp[j],dp[j-a[i]]+a[i]);
		}
	}
	cout << sum-dp[target];
	return 0;
}

3、思路2:暴力法-空间换时间

遍历每一本书,每次选择买与不买,将每本书买与不买的花费存到set容器中,那么最后一个set容器中将保存每一种情况的花费,而set内部又是存在排序的,这样时间复杂度优化为O(n)。

考试的时候写不下去可以往空间换时间的角度来思考。

#include<iostream>
#include<set>
using namespace std;

int main() {
	int n,x;
	cin>>n>>x;
	int a[n+1];
	int sum = 0;
	for(int i = 1; i<=n; ++i) {
		cin>>a[i];
		sum+=a[i];
	}
	set<int> s[n+1];
	s[0].insert(0);
	for(int i = 1;i<=n;++i){
		set<int>::iterator it = s[i-1].begin();
		for(it;it!=s[i-1].end();++it){
			int buy = *it+a[i];
			int noBuy = *it;
			s[i].insert(buy);
			s[i].insert(noBuy);
		}
	}
	set<int>::iterator it = s[n].begin();
	for(it;it!=s[n].end();++it){
		if(*it>=x){
			cout << *it << endl;
			break;
		}
	}
	return 0;
}

根据运行结果我们也可以看出该题使用了空间换时间的策略,但也是可以得到100分的。

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值