硬币问题(DP)

来源:互联网 发布:公需课挂机软件 编辑:程序博客网 时间:2024/05/22 00:48

题目:有n种硬币,面值分别为V1,V2,...Vn,每种都有无限多。给定非负整数S,可以选用多少个硬币,使得面值之和恰好为S?输出硬币数目的最小值!


状态:d[i] 表示i 元最少要换多少个硬币,那么我们便要求d[S}的值;

状态如何转移:i元换零钱V[j](i>=V[j]),后状态变为 d[i-V[j]];

状态选取:选d[j]的值最小的,这样d[S]才会最小,所以d[i]=min{d[i-V[j]],(j为1~n);


1)记忆搜索法:

#include<iostream>#include<cstring>#include<algorithm>using namespace std;int d[5000];int v[4]={0,3,5,7};   int S,n=3;const int INF=1<<7-1;/*  递归法 */int dp(int S)                    //ans=-1表示未计算;{     //ans=0表示到终点;int& ans=d[S]; //ans=-(1<<30)表示走不通;if(ans != -1) return ans;   //也可以用vis[N]数组保存,表示是否计算过; ans=(1<<30);for(int i=1;i<=n;i++)if(S>=v[i])ans=min(ans,dp(S-v[i])+1);return ans;}void print_ans(int S){for(int j=1;j<=n;j++)if(S>=v[j]&&d[S]==d[S-v[j]]+1){printf("%d ",v[j]);print_ans(S-v[j]);break;}}int main(){while(cin>>S){memset(d,-1,sizeof(d));d[0]=0;    //S=0表明已经走到终点可以退出了!; cout<<dp(S)<<endl;print_ans(S);}return 0;}


2)递推法

#include<iostream>#include<cstring>#include<algorithm>using namespace std;int d[5000];int v[4]={0,3,5,7}; int S,n=3;int max_coin[5000],min_coin[5000];const int INF=1<<7-1;int dp(int S){int maxv[5000],minv[5000];memset(minv,INF,sizeof(minv));memset(maxv,-INF,sizeof(maxv));minv[0]=maxv[0]=0;     //0元时,0个硬币 for(int i=1;i<=S;i++)for(int j=n;j>=1;j--)if(i >= v[j]) {if(minv[i]> minv[i-v[j]]+1){minv[i]=min(minv[i],minv[i-v[j]]+1);min_coin[i]=j;}if(maxv[i]< maxv[i-v[j]]+1){maxv[i]=max(maxv[i],maxv[i-v[j]]+1);max_coin[i]=j;}}printf("%d %d\n",maxv[S],minv[S]); }print_ans(int* d,int S){while(S){cout<<v[d[S]]<<' ';S -= v[d[S]];}cout<<endl;} int main(){while(cin>>S){dp(S);//print_ans(max_coin,S);//print_ans(min_coin,S);}return 0;}




0 0