最少钱币数(动态规划)

【问题描述】这是一个古老而又经典的问题。用给定的几种钱币凑成某个钱数,一般而言有多种方式。例如:给定了 6 种钱币面值为 2、5、10、20、50、100,用来凑 15 元,可以用 5 个 2 元、1个 5 元,或者 3 个 5 元,或者 1 个 5 元、1个 10 元,等等。显然,最少需要 2 个钱币才能凑成 15 元。 你的任务就是,给定若干个互不相同的钱币面值,编程计算,最少需要多少个钱币才能凑成某个给出的钱数。

【输入形式】输入可以有多个测试用例。每个测试用例的第一行是待凑的钱数值 M(1 <= M<= 2000,整数),接着的一行中,第一个整数 K(1 <= K <= 10)表示币种个数,随后是 K个互不相同的钱币面值 Ki(1 <= Ki <= 1000)。输入 M=0 时结束。

【输出形式】每个测试用例输出一行,即凑成钱数值 M 最少需要的钱币个数。如果凑钱失败,输出“Impossible”。你可以假设,每种待凑钱币的数量是无限多的。

【样例输入】

15 6 2 5 10 20 50 100 1 1 2 0

【样例输出】

2 Impossible

你知道的,这确实是一个古老而又经典的问题。目前我只会使用动态规划来解决这个问题,而且代码写得很冗长,真是私立马赛啊。

遇到这个问题,一般来说第一想法都会是直接用贪心的思想,先选取当前能选取的最大额钱币数,比如说我有1,2,5三种面额的货币,现在要凑7块钱,那就先选一张5,再选一张2,就可以得到最少钱币数2。但是有时候这样的做法并不能得到最优解,比如我现在货币的面额是1,5和8,现在要凑15元,按贪心的做法,会是8+5+1+1,用了4张,而5+5+5则只用三张。显然贪心并不能解决这个问题。

一刻也没有为贪心的失败悲伤,赶到战场的是——动态规划。首先对动态规划的算法思想有个大概的了解:与分治法类似,将待解决问题分解成若干子问题,先求解子问题,再结合子问题的解得到问题的解。而与分治法不同的点在于,适合使用动态规划来求解的问题分解后得到的子问题一般不是相互独立的,也就是说会有重复。为了避免重复计算,可以使用一个表来记录已经解决的子问题的答案。不管这个子问题在不在最终的答案中,只要算了就填到表里面。以上就是这个算法的基本思想,我在一些文章中也看到有人叫这个算法为记忆化搜索,不知道是不是正确的。

使用动态规划算法求解问题的基本步骤为:

1.判断此问题是否具有最优子结构性质

2.由最优子结构性质建立子问题最优解的递推关系

3.计算最优值

4.构造最优解

以此问题为例,首先判断此问题是否具有最优子结构性质。额,忘记说什么是最优子结构性质了,但是没有关系,马上说。其实就是对于一个问题的最优解,这个问题包含的子问题的解也是最优的。用此问题举例来说,假设给出的货币面额是随便啥,现在要凑c元,那么可以把这个问题分解为凑a元和b元,而且可以确定,当凑c元所用货币数为最少的n张时,凑a元和凑b元所用的货币数也是最少的。因为如果凑a元或者凑b元还有更少的方法时,比如说少一张吧,那凑c元所用货币数也应该相应地减1,因为money[c]=money[a]+money[b]。

接下来进行第二步,构造递推关系式。当要凑的钱数小于最小货币面额min时,显然不可能搞出来,为无解;当要凑的钱数等于最小货币面额min时,应该最少用一张货币;而对于其他有解的情况,其实可以想到,每一种新情况都是在原本的情况下加上某个面额,也就是说money[a]=money[b]+某个货币面额,加上这个问题具有最优子结构,可以得出递推式

dp[i]=min(dp[i],dp[i-type[j]]+1)

接下来两步其实已经不重要了,你知道的,现在很晚了,我不想写了,该睡了🤗🤗🤗(话说我这注释是不是写了还不如不写😶)

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int dp[2010],type[20];
int main(){
	int n,k;
	while(cin>>n){
		if(n==0)break;   //终止条件
		cin>>k;
		for(int i=0;i<k;i++)cin>>type[i];
		sort(type,type+k);   //进行一个排序预处理,为啥要这么做我已经忘了
		memset(dp,0,sizeof(dp));   //初始化一下dp数组
		for(int i=type[0];i<=n;i++){
		    for(int j=k-1;j>=0;j--){
		    	if((i%type[j]==0)||(i-type[j]>=0&&dp[i-type[j]]!=0)){   //关于为什么要有这个if语句,我忘了,哎嘿
		    		if(dp[i]==0)   //你知道的
		    	        dp[i]=dp[i-type[j]]+1;
		    	    else
		    	        dp[i]=min(dp[i],dp[i-type[j]]+1);   //你知道的
				}
			}
		}
		if(dp[n]!=0)
		    cout<<dp[n]<<"\n";
		else
		    cout<<"Impossible"<<"\n";
	}
}  

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值