hdu 2069 动态规划_所有硬币组合


前言

又双叒是一道硬币问题,别问,问就是硬币,安排一波~


一、题目

伟大的传送门
简述:给你五个面值1,5,10,25,50(没错,就认识这五种面值),再给你一个总金额s,求出可以凑成金额s的硬币的所有组合的数量,要求数量在100以内。(天天凑钱数,手机支付它不香吗)

二、解决一波

1.不完全解决方案分析

所谓不完全,就是为了更好地理解dp而忽略num<=100这个限制。(其实就是怕秃…T_T)
题目让求出组合种类的数量,又限制每种组合使用的硬币数量,果断决定,先简单走一波。
分析:
:举一波例子求递推式
(定义 dp[j] 为金额为 j 时组合种类,一种状态,type[i] 为面值种类)
1)i=0,即只使用1分硬币进行组合
初始时dp[0]=1
dp[1]可由dp[0]推导:s=1时,type[i]=1(面值1分),等价于组合中必有一个硬币是1分,从总金额中减去这1分,即退回到了s=0时的情况,如果dp[0]这种状态存在,则dp[1]也会存在。即dp[1]=dp[1]+dp[0];
所以dp[j]=dp[j]+dp[j-1];
2)i=1,即新增了5分硬币
(由于低于5分的金额不能用5分表示,所以j从5分开始)
新增了5分硬币,相当于此时组合中最少要有一个是5分,即求s-5后剩余金额的组合种类(即固定了5分不动,剩余组合可变)加上未增加5分时的组合种类,
dp[j]=dp[j]+dp[j-5];

所以递推式为:dp[j]=dp[j]+dp[j-type[i]];

(从1分到50分,每增加一种面值的硬币,其实就相当于组合种类的总数量=未增加新面值之前的种类+增加一枚新硬币而产生的组合种类,而对于“增加一枚新硬币而产生的组合种类”其实就是在产生的新组合中,必有一张是新增进来的硬币,则只需考虑总面额s-新硬币剩下的金额的组合种类就可以

2.不完全解决方案代码

#include<iostream>
#include<stdio.h>
using namespace std;
//没有硬币数量的限制 
const int money=251;
int type[5]={1,5,10,25,50};
int dp[money]={0};

void solve(){//打一波表 
	dp[0]=1;
	for(int i=0;i<5;i++){
		for(int j=type[i];j<money;j++){
			dp[j]=dp[j]+dp[j-type[i]];
		}
	} 
} 
int main(){
	int s;
	solve();
	while(cin>>s){
		cout<<dp[s]<<endl;
	}
}

3.完全解决方案分析

终究逃不过不大于100这个限制
:由上面那种解决方案可知,dp[i]这个状态太简单,没有记录计算过程的细节,所以这里重新定义一个dp[i][j],建立一个转移矩阵,即表示可以用 j 个硬币组合凑成金额 i,这样只需在打表的时候对j加个限制条件就可了。
推一波~:
1)只用1分硬币实现
初始化dp[0][0]=1,定义type[5]={1,5,10,25,50};五种面值
dp[1][1]是在dp[0][0]的基础上,对金额+1,硬币数量+1,后的转移状态,转移后组合的方案不变,值为dp[1][1]=dp[0][0]=1;
但要考虑其原有方案数,即在没加入新硬币之前能凑成金额i的组合。
递推式:dp[1][1]=dp[1][1]+dp[0][0]=dp[1][1]+dp[1-1][1-1]=1;
再深一步:dp[1][1]=dp[1][1]+dp[1-type[0]][1-1]
2)新来一枚5分硬币
dp[i][j],这里当i<5时,不可能会用到5分硬币,
i>=5时,对于金额为i,硬币数量为j的组合数量其实就是从i中减去5分这个硬币(因为其实就是固定组合中肯定会至少有一个5分,看其他硬币的组合情况),而对于j-1,即j个硬币中减去5分这一个硬币(因为你去掉了一个5分的硬币)
递推式:dp[i][j]=dp[i][j]+dp[i-type[1]][j-1];

两种方案相类似,最终递推式为:dp[i][j]+=dp[i-type[k]][j-1];

最后的最后,要输出组合数量,总的组合数量就是所有纵坐标对应的值相加,毕竟纵坐标j记录的就是在总金额相同的情况下,使用不同数量硬币所对应的组合数。

4.完全解决方案代码

今天又是AC的一天~

#include<iostream>
#include<stdio.h>
using namespace std;

const int coin=101;
const int money=251;
int dp[money][coin]={0};
int type[5]={1,5,10,25,50};

void solve(){
	dp[0][0]=1;
	for(int i=0;i<5;i++){//面值遍历 
		for(int j=1;j<coin;j++){//不大于100的限制定义给了j 
			for(int k=type[i];k<money;k++){//对每种金额打表 
				dp[k][j]+=dp[k-type[i]][j-1];//递推式 
			}
		}
	}
}
int main(){
	int s;
	int ans[money]={0};
	solve();
	for(int i=0;i<money;i++){//打表 
		for(int j=0;j<coin;j++){
			ans[i]+=dp[i][j];//加和,求出总金额相同的情况下,使用不同数量硬币所对应的组合数 
		} 
	}
	while(cin>>s){
		cout<<ans[s]<<endl;
	}
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值