算法——硬币选择问题(dp、贪心)
来源:互联网 发布:真丝睡衣 知乎 编辑:程序博客网 时间:2024/05/15 01:47
本题来自2015级算法第四次上机
A
怠惰的王木木Ⅱ
时间限制:1000ms 内存限制:65536kb
通过率:1/165 (0.61%)
正确率:1/416 (0.24%)
之前练习赛做过一道硬币找零问题,它的面值是1,5,10,50,100,500,完全的贪心题型,为什么这里可以用贪心,我们知道,贪心是有条件的,在本题中贪心策略的前提非常简单,即大额硬币必须是所有面值小于它的硬币的面值倍数。原因:http://blog.csdn.net/hwdopra/article/details/6318746。
那么本题呢?面值分别是1,5,10,20,50,100,这就不符合之前贪心的使用条件,比如输入(10,0,0,3,1,0,60,),60=50*1+10*1,60=20*3,就本题来说,要解决这个问题还是不难的,对50面值的特殊对待,我就从0~coinNum[4]循环一边,其他的依然贪心处理。这个问题就解决了,代码如下(DP内为动态规划做法)。
//// main.cpp// 怠惰的王木木Ⅱ//// Created by Angus on 2017/10/24. // Copyright © 2017年 Angus. All rights reserved.//#include <iostream>#include <cmath>//#define DPusing namespace std;#ifdef DP#define INF 0x3f3f3f3fint V[10] = {0, 1, 5, 10, 20, 50, 100};//硬币面值long long C[10];//硬币对应的个数long long dp[1005][100005];long long A;void solve(){ long long n = 6; for(int i = 1;i <= n;i++)//初始化 { for(int j = 0;j <= A;j++) { if(j == 0)//找出0块钱 dp[i][j] = 0; else if(i == 1)//最小面值初始化 { if(j % V[i] == 0 && j / V[i] <= C[i]) dp[1][j] = j / V[i]; else dp[1][j] = INF; } else dp[i][j] = INF; } } for(int i = 2;i <= n;i++)//构造dp数组 { for(int j = 1;j <= A;j++) { for(int k = 0;k <= C[i];k++) { if(dp[i][j] > dp[i-1][j - k * V[i]] + k && j - k * V[i] >= 0) dp[i][j] = dp[i-1][j - k * V[i]] + k; } } }// for (int i = 0; i <= n; i++)// {// for (int j = 0; j <= A; j++)// {// if(dp[i][j] == INF)// printf("INF ");// else// printf("%lld ",dp[i][j]);// }// printf("\n");// } if(dp[n][A] != INF) printf("%lld\n",dp[n][A]); else printf("error\n");}int main(){ while (~scanf("%lld%lld%lld%lld%lld%lld%lld",&C[1],&C[2],&C[3],&C[4],&C[5],&C[6],&A)) { solve(); } return 0;}#endif//贪心算法int V[6] = {1, 5, 10, 20, 50, 100};//硬币面值long long C[6];//硬币对应的个数long long A, B;void Solve()//求最小需要的硬币数{ long long ans1 = 0, ans2 = 9999; long long t = min(A / V[5], C[5]); A -= V[5] * t; ans1 = t; B = A; for (int i = 0; i <= min(B / V[4], C[4]); i++) { A = B; A -= V[4] * i; long long tmp = 0; for(int j = 3; j >= 0; j--) { long long k = min(A / V[j], C[j]); A -= V[j] * k; tmp += k; //printf("%lld\n",k); } if(A == 0) ans2 = min(ans2,tmp + i); } cout << ans1 + ans2 <<endl;}int main(){ while (~scanf("%lld%lld%lld%lld%lld%lld%lld",&C[0],&C[1],&C[2],&C[3],&C[4],&C[5],&A)) { Solve(); } return 0;}
但是,不能满足于此,如果给任意面值任意数量呢?我们知道,贪心可以说是动态规划的一小类特殊问题的解法,所以这里要用动态规划。
问题定义:dp[i][j] 表示当前i种硬币找j块钱最少硬币数,dp[n][m] 是我们要求的最终结果。
那么如何把这个大问题分解成小的同类问题?dp[i][j]=min{dp[i][j],dp[i-1][j–k*CoinValue[i]]+k},条件k<[0, coinNum[i]]&&j-k* coinValue[i] >= 0,即对每一个硬币都遍历一次。
举个例子,coinValue[]= {1,2,5},每一种硬币的数量是有限的,分别是CoinNum[]={3, 3, 3},给定一个数值m=18, dp[3][ 18] = min{dp[2][18] + 0,dp[2][18-1*5]+1, dp[2][18-2*5] + 2,dp[2][18- 3* 5] + 3},也就是说dp [3][18]可以分解为当对于5块值得硬币选0个,选1个,选2个,选3个之后的子问题,当选完5这种硬币时,接下来只有1块和2块的硬币可选, 于是子问题就变成dp[2][18],dp[2][13],dp[2][8], dp[2][3]。
注:参考代码二是普遍情况:求n中面值找出m零钱的最小硬币数,非AC代码,可做模板。
参考资料:http://blog.csdn.net/suwei19870312/article/details/9296415。#include<cstdio>#define INF 0x3f3f3f3fint dp[11][20002];//dp[i][j]前i种硬币找j块钱最少硬币数int coinValue[11],coinNum[11];//面值及其数量int main(){ int i,j,k,n,m;//n种面值,找出m块零钱 while(~scanf("%d",&n)) { for(i=1;i<=n;i++)//按面值升序输入 scanf("%d%d",&coinValue[i],&coinNum[i]); scanf("%d",&m); for(i=1;i<=n;i++)//初始化 { for(j=0;j<=m;j++) { if(j==0)//找出0块钱 dp[i][j]=0; else if(i==1)//最小面值初始化 { if(j%coinValue[i]==0&&j/coinValue[i]<=coinNum[i]) dp[1][j]=j/coinValue[i]; else dp[1][j]=INF; } else dp[i][j]=INF; } } for(i=2;i<=n;i++)//构造dp数组 for(j=1;j<=m;j++) for(k=0;k<=coinNum[i];k++) if(dp[i][j]>(dp[i-1][j-k*coinValue[i]]+k)&&(j-k*coinValue[i])>=0) dp[i][j]=dp[i-1][j-k*coinValue[i]]+k;// for (int i = 0; i <= n; i++)// {// for (int j = 0; j <= m; j++)// {// if(dp[i][j] == INF)// printf("INF ");// else// printf("%d ",dp[i][j]);// }// printf("\n");// } if(dp[n][m]!=INF) printf("%d\n",dp[n][m]); else printf("-1\n"); } return 0;}
问题拓展
硬币组合问题
假设现有3种硬币,{1,2,5}, 但是每种硬币的个数没有限制,可以是无限,现在问要筹成18, 有多少种组合方式?
思路: 动态规划。
问题定义:
dp[n][m]表示当目标值为n, 有m种硬币可选的时候的组合数,同样,dp[18][3]是我们要求的最终结果。
同样,dp[n][m] = sum{dp[n – i*CoinValue[m]][m-1]} 条件 n-i*CoinValue[m] >=0
那么dp[18][3] =dp[18][2] + dp[13][2] + dp[8][2] + dp[3][2].
也就是说dp[18][3]可以分解为当对于5分值得硬币选0个,选1个,选2,选3个之后的子问题,当选完5这种硬币时,接下来只有1分和2分的硬币可选, 于是子问题就变成为dp[18][2],dp[13][2], dp[8][2], dp[3][2].
代码:http://blog.sunchangming.com/post/54899551762
- 算法——硬币选择问题(dp、贪心)
- 贪心算法——硬币问题
- 贪心算法(硬币问题)
- 贪心算法:硬币问题
- 最少硬币问题-贪心选择
- 最少硬币问题--贪心算法
- 贪心算法之硬币问题
- 硬币问题(贪心)
- 硬币问题 (贪心)
- 贪心算法—活动选择问题
- 贪心算法—活动选择问题
- 活动选择问题—贪心算法
- 硬币问题(DP)
- 硬币找零(贪心算法)
- C++典型贪心算法--找硬币问题
- 使用贪心算法实现硬币找零问题
- 贪心算法解硬币找零问题
- ACM_程序设计竞赛:贪心算法:硬币问题
- 51nod 1290 Counting Diff Pairs(莫队+树状数组)
- 属性动画
- 图像拼接(Image Stitching)问题的研究史
- 【网安随笔】ctf-word隐写
- alpine linux填坑之路(一)
- 算法——硬币选择问题(dp、贪心)
- WebSockte
- 神经网络相关名词解释
- 51nod 1678 lyk与gcd mobius反演
- 选择排序
- 查询算法
- 8月英语学习总结
- hibernate的增删改查和三种状态
- Centos7安装部署Redis3.2.9 (普通用户)