问题描述:现有面值为3,4,5的三种硬币(每种硬币可以重复使用),现在去商店买一个东西要X元,问如何用最少的硬币付清,而不需要对方找钱,如果无法用手中的硬币恰好凑出X则打印-1.
样例:
输入: 1 2 3 6
输出:-1 -1 1 2
分析:
有一个很直觉的思路就是先尽量用大的硬币,然后用小的硬币,举个栗子:这个题中13元可以分解为先用一个5元,再用一个5元,此时发现再用一个3元硬币就行了。哦豁,妙哇,完美解决了。
别急,举个反例看看:27元用2,5,7三种硬币,你会觉得用3个7再用3个2,一共6个硬币就行了吗。其实呢,最少的是1个7和4个5,才5个硬币。这说明贪心策略只是局部最优解而不是全局最优解,而且是暴力,不太具有普遍性。
如果用递归法的话,会发现有很多重复计算,而且空间开销大,也欠妥。
下面列出这道题的动态规划思路步骤:
一,确定状态
1、可以确定a1+a2+a3+...+ak=x元一定成立,且一定有最后的一枚面值为ak的硬币,而且除掉最后一枚硬币前面的所有硬币面值加起来一定是x-ak元
注意:不需要知道前面k-1个硬币具体是怎么样的,甚至不需要知道k和ak,但是可以确定前面的硬币拼了x-ak元
2、最优子结构:先不看最后一个ak硬币,而是要确保前面的x-ak元的拼凑方法一定是用硬币最少的!!!
二、列出状态转移方程(DP的核心)。
设f(x)=凑出X元最少用的硬币数
这道题不难推出:f(x)=min{f(x-3)+1,f(x-4)+1,f(x-5)+1};
本题的具体数据:
f(0) f(1) f(2) f(3) f(4) f(5) f(6) f(7) f(8) f(9) f(10) f(11)......
需要的硬币数 0 max max 1 1 1 2 2 . 2 2 2 3 ....
解释:
2=1*2;3=1*3;4=1*4;5=1*5;6=3*2;7=3+4;8=3+5或者4+4(但都是两枚) ;9=5+4;11=4+4+3(3枚)
三、找出初始条件
初始:b[0]=0,0元不用硬币
四、从小到大计算。
依次算出b[1],b[2],b[3]。。。其中后面的是通过前面的推出来的,不要从大往小推(b[n],b[n-1]...b[2],b[1]),至于为什么可以仔细想想。
代码写的有点杂。。。主要是确定状态转移方程中最优子结构时用了辅助变量fl和min
#include<stdio.h>
#define max 100000
int b[10000];
int main()
{
int a[3]={3,4,5};
int n,i,j;
scanf("%d",&n)//输入总价格
b[0]=0;//初始化0元不需要硬币
for(i=1;i<=n;i++)//每次循环确定一个b[i]的值,b[i]要么是max(凑不出硬币),要么是一个比较小的数
{
int min=0;
b[i]=max;//开始时b[i]每一个都赋值为max,默认是凑不出硬币,如果能凑出硬币则b[i]的值发生改变
int fl=0;
for(j=0;j<3;j++)
{
if(i-a[j]>=0&&!fl)
{
min=i-a[j]+b[i-a[j]]+1;
fl=1;
}
if(i-a[j]>=0&&fl)
{
if(b[i-a[j]]!=max)
{
int sum=b[i-a[j]]+1;
if(sum<min)
min=sum;
}
}
}
if(min>0)
b[i]=min;
}
if(b[n]>=max)//这是不能凑出硬币的情况打印-1表示
printf("-1");
else
printf("%d",b[n]);
}