递归——凑单

描述
消费者为了享受商家发布的满减优惠,常常需要面临凑单问题。

假设有n件商品,每件的价格分别为p1,p2,...,pn,每件商品最多只能买1件。为了享受优惠,需要凑单价为t。那么我们要找到一种凑单方式,使得凑单价格不小于t(从而享受优惠),同时尽量接近t。被称为“最佳凑单”

如果不存在任何一种凑单方式,使得凑单价格不小于t(即无法享受优惠),那么最佳凑单不存在。

比如当前还差10元享受满减,可选的小件商品有5件,价格分别为3元、5元、8元、8元和9元,每件商品最多只能买1件。那么当前的最佳凑单就是11元(3元+8元)。
 
关于输入
第一行输入商品数n(n<=10)和需要凑单价t,如:
5 10
第二行输入每件商品的价格,如:
3 5 8 8 9
 
关于输出
如果可以凑单成功,则输出最佳凑单的价格,如:
输入
5 10
3 5 8 8 9
输出
11

如果无法凑单成功,则输出0。

如输入
5 10
1 1 1 1 1
则无法凑够10元,输出
0
例子输入
5 10

3 5 8 8 9
例子输出
11
提示
1. 为了简化问题,本题中每件商品最多只能买1件。
2. 不同的商品,即使价格相同也可以都买。比如5件商品,价格分别为3元、5元、8元、8元和9元。你可以买 8元(第3件)+8元(第4件)

做法1:k个商品凑t元=min(不选第k个 剩下k-1个凑t,选第k个 剩下k-1个凑t减去k的价值)
边界!!k=0:比较第一个商品价值与此时的额度money

#include<iostream>	
using namespace std;
int value[10] = {};
int n, t;
int f(int money, int k)//挑选前k个商品,要凑的额度是money,凑单结果是返回值
{
	if (k == 0 && value[0] < money)return 0;//额度没用完,也就是没凑齐,由题意返回0
	if (k == 0 && value[0] >= money)return value[0];//凑齐了,返回当前凑单的值
	int a = f(money, k - 1);//不选第k个产品的凑单价值
	int b = f(money - value[k], k - 1) + value[k];//选第个产品的凑单价值
	if (a < money && b < money)return 0;//如果都没能凑成额度,返回0
	else if (a >= money && b < money)return a;//只有a可以,那么直接返回a
	else if (a < money && b >= money)return b;
	else if (a >= money && b >= money)return (a < b ? a : b);//都可以,返回较小的那一个
}
int main() {
	cin >> n >> t;
	for (int i = 1; i <= n; ++i) {
		cin >> value[i];
	}
	cout << f(t, n);
	return 0;
}

做法2:尝试所有可能的凑法并记录,再比较

#include<iostream>
#include<algorithm>
using namespace std;
int n, t, num = 0;
int cost[11] = {};
int money[200] = {};
void f(int x, int y)//第x个商品,已凑y元
{
	if (y >= t) {//如果凑够了,将这种凑法的价格记录并返回
		money[num++] = y;
		return;
	}
	if (x == n) {//如果所有商品均尝试,返回
		return;
	}
	f(x + 1, y + cost[x]);//选择第x个商品,继续凑单
	f(x + 1, y);//选择第x个商品,继续凑单
}
int main()
{
	cin >> n >> t;
	for (int i = 0; i < n; ++i) {
		cin >> cost[i];
	}
	f(0, 0);//从第一个商品开始凑单,初始0元
	sort(money, money + num);//将所有凑单价格排序
	if (money[0]) {
		cout << money[0];
	}
	else cout << 0;
	return 0;
}

### 关于蓝桥杯 C++ 数的划分问题 #### 解题思路 数的划分问题是典型的递归与回溯相结合的问题。这类题目通常要求将一个整数拆分成若干个正整数之和,并统计不同的拆分方案数量。 对于此类问题,可以采用深度先搜索(DFS)的方法求解。具体来说,在每一步尝试减去一个小于等于当前剩余数值的最大可能值作为新分支继续探索直到达到终止条即当剩下的数字正好为零时记录一次有效分割路径;反之则返回上一层重新选择其他可能性进行试探直至遍历完所有潜在组合形式[^2]。 此外,考虑到可能存在重复计算的情况,可以通过记忆化技术来提高效率——利用缓存保存已经访问过的状态的结果从而避免不必要的重复工作量。不过本题由于数据规模较小,直接暴力枚举也能接受范围内完成任务[^3]。 #### 代码示例 下面给出了一段基于上述思想编写的C++源码: ```cpp #include <iostream> using namespace std; // 定义全局变量存储最终结果 int ans = 0; const int MAXN = 60 + 5; // 设定最大测试范围加一点冗余空间 bool vis[MAXN]; // 记录某位置是否已被占用 void dfs(int rest, int last) { if (rest == 0) { // 当前剩余待分配额度清零说明找到了一组可行解 ++ans; return ; } for (int i = min(rest, last); i >= 1; --i) { // 枚举下一个可选元素大小并保证非增序排列特性 dfs(rest - i, i); } } int main() { int N; cin >> N; dfs(N, N); // 初始化调用入口参数设置成总数目本身以及允许选取上限同样也是它自己 cout << ans << endl; return 0; } ``` 这段程序实现了对给定自然数`N`的不同分解方法数目统计功能。其中核心部分在于递归函数`dfs()`的设计,其接收两个参数分别代表还需齐多少金额(`rest`)以及本次递归之前所取最小面额是多少(`last`)以便维持序列调不升性质防止出现相同模式多次计入答案之中造成错误计数现象发生[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值