贪心算法-进阶找零问题

题干:有张数不限的n种面额纸币,求纸币总张数的最小值num,使得这num张纸币可以组合出1至x的任何金额。

输入:正整数n和x,n个表示面额的正整数。

n<=20,x<=10000,面额<=10000

输出:最少需要携带的零钱张数。 若不存在满足条件的方案,则输出-1。

要点:

1.对某个目标值最优不一定全局最优,不能按照一般找零问题的方法使用贪心

2.如果面值有1,一定能组合出所有目标值;如果没有1,一定不能组合出1。所以判断是否存在满足条件方案只需要判断面值是否有1。

从一开始就可以判断出所有目标值均有解,帮助了下文中问题1的解决。

错误思路:分别枚举每个目标金额的所有组成方案,再对所有方案进行排列组合,求出最少张数。

vector<vector<vector<pair<int, int>>>>target;//所有target组成一个vector
vector<vector<pair<int, int>>>solutions;//一个target的所有solution组成一个vector
vector<pair<int, int>> value_num;//一个solution的所有value_num组成一个vector
pair<int, int>tmp_pair;//一个value_num的value和num组成一个pair

问题1:如何枚举所有组成方案?

一般思路是,可以遍历第每面值取0至tmp_target/value

tmp_target-=value_num[i].first*value_num[i].second;

但可以发现tmp_target=0则该方案已完成,tmp_target>=1则必有解

此时只需要获取已求得的target[tmp_target-1]即可。

所以对每个target[tgt-1],有n种情况,每次取value_num[i].first<=target一张,得到tmp_target=tgt-value_num[i].first。

若tmp_target=0,记为一种方案;

若tmp_target!=0,将target[tmp_target-1]的所有solution对应的value_num[i].second++即为target[tgt-1]在该种情况下的组合方案。

问题2:如何枚举所有组合方案的组合?

这步时间复杂度估计为O(n!),不可能全部枚举。注意到难点1的思路,其实

后面target的组合方案都是在前面某个方案的基础上加1张即可,总数是单调的。所以,应该从1开始,每次选择最优方案,并更新value_num。

对每个target,若当前value_num可以组成target,继续;若不能,选择一张尽可能大的面值。

   怎么判断是否可以组成target?只要判断当前纸币总价值tmp是否大于等于target。实际上tmp应该满足target<=tmp<2*target,但是可以证明tmp一定小于2*target。该条件下一定可以减去一个小于该值的数(这些纸币一定可以组成小于它的所有target),使得剩余纸币总价值即为该值。

          怎么证明val一定小于2*target?若tmp0<target1,则选取一张面值小于target1的纸币,更新为tmp1<2*target1<2*target2

   为什么要选尽可能大的面值?

1.对于小于最大面值的target,大面值可以与原来的小面值组成后面一或多个target;

2.对于大于最大面值的target,每个都可以看做某个小于最大面值的target加上最大面值max的倍数。应当选择最大的面值,这样对于每max个数,只需要多加一张纸币,递增速度会慢。

vector<vector<pair<int, int>>>solution;//所有target的当前最优solution组成一个vector
vector<pair<int, int>> value_num;//一个solution的所有value_num组成一个vector
pair<int, int>tmp_pair;//一个value_num的value和num组成一个pair

代码实现

#include<bits/stdc++.h>
//#include<iostream>
using namespace std;
vector<vector<pair<int, int>>>solution;//所有target的最优solution组成一个vector
vector<pair<int, int>> value_num;//一个solution的所有value_num组成一个vector
pair<int, int>tmp_pair;//一个value_num的value和num组成一个pair
int main()
{
	int n, x,num=0;
	cin >> n >> x;
	for (int i = 0; i < n; i++)//输入面值
	{
		cin >> tmp_pair.first;
		value_num.push_back(tmp_pair);
	}
	sort(value_num.begin(), value_num.end());//按从小到大排序面值
	if(value_num[0].first!=1)
	{
		cout << "-1";
		return 0;
	}
	solution.push_back(value_num);
	for (int tgt = 1; tgt <= x; tgt++)//遍历每个目标值
	{
		int tmp = 0;
		for (int i = 0; i < n; i++)
			tmp += value_num[i].second * value_num[i].first;
		if (tmp >= tgt)
		{
			solution.push_back(value_num);
			continue;
		}
			if (value_num[n-1].first < tgt)
			{
				solution.push_back(solution[tgt - value_num[n-1].first]);
				solution[tgt][n-1].second++;
				if(value_num[n - 1].second< solution[tgt][n - 1].second)
					value_num[n-1].second++;
			}
			else
			{
					for (int i = n - 1; i >= 0; i--)
						if (value_num[i].first <= tgt)
						{
							solution.push_back(solution[tgt - value_num[i].first]);
							solution[tgt][i].second++;
							value_num[i].second++;
							break;
						}

			
			}
	}
	for (int i = 0; i < n; i++)
		num += value_num[i].second;
	cout << num;
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值