最少硬币问题描述
有n种硬币,面值分别为V1,V2,…,Vn.数量无限。输入非负整数S,请你选用硬币,使其和为S。要求输出最少的硬币组合。
解题思路
先定义一个数组int cnt[M],其中 cnt[i] 是金额 i 对应的最少硬币数量。如果程序能计算出所有的 cnt [i],0< i <M,那么对输入的某个金额 i,只要查 cnt[i] 就得到了答案。
那么我们该如何计算 cnt[i] 呢?cnt[i] 和 cnt[i-1]又是否有关系呢?这里涉及到一个“递推”的思路,从小问题 cnt[i-1] 的解决递推到大问题 cnt[i] 的解决。小问题的解决能推导出大问题的解决。下面我们以5种面值[1,5,10,25,50]的硬币为例,讲解从i-1到 i 的递推过程:
(1) 只使用最小面值的 1 分硬币
(2)在使用面值1分硬币的基础上,增加使用第二大面值的5分硬币
(3)继续处理其它面值的硬币
在DP中,把 cnt[i] 这样的记录子问题最优解的数据称为“状态”;从 cnt[i-1] 或 cnt[i-5] 到 cnt[i] 的递推,称为“状态转移”。用前面子问题的结果,推导后续子问题的解,逻辑清晰、计算高效,这就是DP的特点。
参考代码
#include<bits/stdc++.h>
using namespace std;
const int M = 251; //定义最大金额
const int N = 5; //5种硬币
int type[N] = {1, 5, 10, 25, 50}; //5种面值
int cnt[M]; //每个金额对应最少的硬币数量
const int INF = 0x1FFFFFFF;
void solve(){
for(int k = 0; k< M; k++) //初始值为无穷大
cnt[k] = INF;
cnt[0] = 0;
for(int j = 0; j < N; j++)
for(int i = type[j]; i < M; i++)
cnt[i] = min(cnt[i], cnt[i - type[j]] + 1); //递推式
}
int main(){
int s;
solve(); //计算出所有金额对应的最少硬币数量。打表
while(cin >> s){
if(cnt[s] == INF) cout <<"No answer" << endl;
else cout << cnt[s] << endl;
}
return 0;
}
打印最少硬币的组合
#include<bits/stdc++.h>
using namespace std;
const int M = 251; //定义最大金额
const int N = 5; //5种硬币
int type[N] = {1,5,10,25,50}; //5种面值
int cnt[M]; //每个金额对应最少的硬币数量
int path[M]={0}; //记录最小硬币的路径
const int INF = 0x1FFFFFFF;
void solve(){
for(int k=0; k< M;k++)
cnt[k] = INF;
cnt[0]=0;
for(int j = 0;j < N;j++)
for(int i = type[j]; i < M; i++)
if(cnt[i] > cnt[i - type[j]]+1){
path[i] = type[j]; //在每个金额上记录路径,即某个硬币的面值
cnt[i] = cnt[i - type[j]] + 1; //递推式
}
}
void print_ans(int *path, int s) { //打印硬币组合
while(path[s]!=0 && s>0){
cout << path[s] << " ";
s = s - path[s];
}
}
int main() {
int s;
solve();
while(cin >> s){
if(cnt[s] == INF)
cout <<"No answer"<<endl;
else{
cout << cnt[s] << endl; //输出最少硬币个数
print_ans(path,s); //打印硬币组合
}
}
return 0;
}