题意描述:
假设有几种硬币,如1、3、5,并且数量无限。请找出能够组成某个数目的找零所使用最少的硬币数。
输入格式:
第一行两个整数数t,n,表示总金额和面值总数;
第二行n个整数a1,a2…an,其中ai表示第i种硬币的面值。
输出格式:
最少硬币总数。如果无解,输出-1。
样例输入:
15 3
1 11 5
样例输出:
3
思路分析:
1.暴力搜索
将最小值设定为一个很大的数INF,然后设立一个递归函数dfs(int amount)求解即可,其中amount代表当前待找钱的数额,递归使用dfs(amount-mz[i]),其中mz[i]代表第i种硬币的面值,递归结束条件为amount ==0,<0代表当前面值无解。全部结束后如果最小值仍为INF则无解。
此解法会造成大量重复计算,因而耗费时间。
代码就不贴了。
2.记忆化搜索
可以用记忆化搜索节省时间。
设立记忆数组vis,开始全部初始化为-1,递归函数改为int型,amount ==0时就不用找了,返回0,小于0时返回-1,此时代表当前面值无解,minn仍为INF,记忆数组中对应项仍为-1。否则就要改变。
代码:
#include <cstdio>
#include <algorithm>
#define M 51 //硬币最多50种
#define MAX_VALUE 10001 //金额最多1万
#define INF 0xffff0
using namespace std;
int mz[M],n,cnt=0;
int vis[MAX_VALUE];
int dfs(int amount){
int minn=INF; //必须为局部变量,防止改变
if(amount<0)
return -1;
if(amount==0){
return 0;
}
if(vis[amount]>=0)
return vis[amount];//记忆化搜索
for(int i=0;i<n;i++){
int k=dfs(amount-mz[i]);
if(k!=-1)
minn=min(minn,k+1); //返回-1证明当前金额没有符合题意的解,minn会维持为INF方便下面返回,另:若有解要加上当前硬币
//另:minn不能设为-1
}
if(minn!=INF)
vis[amount]=minn; //必须在外面返回,否则循环执行一次就会终止,另外,如果minn为INF就证明当前金额没有符合题意的解,无需更新vis[amount]
return vis[amount];
}
int main(){
int t;
fill(vis,vis+MAX_VALUE,-1); //一开始将所有值设为-1,如果没有解直接输出也是正确的
scanf("%d%d",&t,&n);
for(int i=0;i<n;i++)
scanf("%d",&mz[i]);
dfs(t);
printf("%d",vis[t]);
return 0;
}
3.动态规划
上面的递归保存了求得的值,属于动态规划的思想。但仍可以将递归改写为递推求解。
按照个人理解,递归是“自顶向下”将原问题“分解”,如本题中将总额t分解直至0,而动态规划则是“自底向上”将子问题“合成”为原问题的解。
#include <cstdio>
#include <cmath>
#include <algorithm>
#define M 51 //硬币最多50种
#define MAX_VALUE 10001 //金额最多1万
#define MAX 0xffff0
using namespace std;
int mz[M],k,cost[MAX_VALUE]; //mz代表面值,cost[i]为找零i元需要的硬币数最小值
void change(int x,int *used) {
int i,j,k=-1;
fill(cost,cost+M,MAX);
int sum=0; //从1开始循环
for(i=1; i<=x; i++) { //要找的钱数
for(j=0; j<k; j++) {
if(i>=mz[j]) {
k=cost[i];
cost[i]=min(cost[i],cost[i-mz[j]]+1); //状态转移方程,要+1选上当前物品
if(k!=cost[i]) {
used[i]=mz[j];
}
}
}
}
return cost;
}
int main() {
int n,i/*,used[1000]= {0}*/; //used[i]为找零i最后找出的钱
scanf("%d",&n);
// int m=n;
for(i=1;i<=n;i++)
scanf("%d",&mz[i]);
if(cost[n]==MAX)
printf("-1");
else {
printf("%d",cost[n]);
/* while(m>0) { //输出找零金额
printf("%d ",used[m]);
m-=used[m];
}
printf("\n");*/
}
return 0;
}