货币兑换问题--动态规划策略求解

问题描述

考虑下面的货币兑付问题:在面值为 ( v 1 , v 2 , ⋯   , v n ) \left(v_1,v_2,\cdots,v_n\right) (v1,v2,,vn)的n种货币中,需要支付 y y y值的货币,应如何支付才能使货币支付的张数最少,即满足: ∑ i = 1 n x i v i = y \sum_{i=1}^{n}{x_iv_i=y} i=1nxivi=y,且 ∑ i = 1 n x i \sum_{i=1}^{n}x_i i=1nxi最小。

输入

(1)货币种类的个数;
(2)从小到大输入货币的价值(其中第一个必须为1);
(3)要兑换的钱的价值(整数)。

输出

兑换的货币个数的最小值

测试用例

输入(Input)
6
1 2 5 10 20 50
46
输出(Output)
兑换的最小货币个数是4

解题思路

本题使用动态规划策略进行算法设计。

1.划分子问题

原问题:使用货币 ( v 1 , v 2 , ⋯   , v n ) (v_1,v_2,\cdots ,v_n) (v1,v2,,vn)凑出总金额为 m m m的需要的钱币最少数量

int exchange(n,[v1,v2,...,vn],m);

子问题:使用货币 ( v 1 , v 2 , ⋯   , v j ) , j ≤ n (v_1,v_2,\cdots ,v_j),j \leq n (v1,v2,,vj),jn凑出总金额为 i ( i ≤ m ) i(i \leq m) i(im)的需要的钱币最少数量

int exchange(j,[v1,v2,...,vj],i);

第1阶段,j=1,考虑钱币面值的所有可能取值为 ( v 1 , ) (v_1,) (v1,),凑出总金额为m需要的钱币数量。
第2阶段,j=2,考虑钱币面值的所有可能取值为 ( v 1 , v 2 , ) (v_1,v_2,) (v1,v2,),凑出总金额为m需要的钱币数量。
第j阶段,j=j,考虑钱币面值的所有可能取值为 ( v 1 , v 2 , ⋯   , v j , ) (v_1,v_2,\cdots,v_j,) (v1,v2,,vj,),凑出总金额为m需要的最少钱币数。

第n阶段,j=n,考虑钱币面值的所有可能取值为 ( v 1 , v 2 , ⋯   , v j , ⋯   , v n ) (v_1,v_2,\cdots,v_j,\cdots ,v_n) (v1,v2,,vj,,vn),凑出总金额为m需要的最少钱币数。

2.找递推关系(书上的说法叫动态规划函数)
决定第 j j j个序列 ( v 1 , v 2 , ⋯   , v j ) (v_1,v_2,\cdots, v_j) (v1,v2,,vj)的解,是在第 j − 1 j-1 j1个序列 ( v 1 , v 2 , ⋯   , v j − 1 ) (v_1,v_2,\cdots,v_{j-1}) (v1,v2,,vj1)已经完成的基础上,有三种情况:

  • j j j个面值的货币 v j v_j vj=需要支付的金额 i i i,要完成支付只需要1张, Q [ i ] [ j ] = 1 Q[i][j]=1 Q[i][j]=1
  • j j j个面值的货币 v j v_j vj>需要支付的金额 i i i, 此时问题的解与 j − 1 j-1 j1是一样的。 Q [ i ] [ j ] = Q [ i ] [ j − 1 ] Q\left[i\right]\left[j\right]=Q\left[i\right]\left[j-1\right] Q[i][j]=Q[i][j1]。(比如要支付的金额是5元,这货币面值是10元,显然咱们不能做赔本买卖,这是自然就会想到用更少的面值的货币进行交易,问题与上一阶段是同解的)
  • j j j个面值的货币 v j v_j vj<需要支付的金额 i i i,说明 v j v_j vj可以用于支付,而且使用 v j v_j vj支付之后剩下的需要支付的金额变为 i − v j i-v_j ivj,问题转化为 Q [ i − v j ] [ j ] + 1 Q\left[i-v_j\right]\left[j\right]+1 Q[ivj][j]+1

3.填表
表格Q是二维的,m+1行,n+1列,存放的是每一个子问题的解。

编程实现

#include<iostream>
using namespace std;

int exchange(int n, int *v, int money) {
	int i,j;
	int Q[2000][20]; //动态规划表
	for(i=0; i<=money; i++) Q[i][0]=0; //初始化首行 咱没钱,还是不要做生意了 
	for(j=0; j<=n; j++) Q[0][j]=0;     //初始化首列 老板破产了,咱想做生意都不行
	for(i=1; i<=money; i++) {
		for(j=1; j<=n; j++) {
			if(v[j]==i)
				Q[i][j]=1;
			else if(v[j]>i) 
				Q[i][j] = Q[i][j-1];
			else
				Q[i][j] = Q[i-v[j]][j] + 1; 
		}
	}	
	return Q[money][n];
}
int main() 
{
	int n,i,v[10],money;
	cin >> n; // 我们钱币面值有n种
	for(i=1; i<=n; i++)
		cin >> v[i];
	cin >> money; 
	cout << "兑换的最小货币个数是" << exchange(n,v,money) << endl;
	return 0;
} 

运行测试
输入

6
1 2 5 10 20 50
46

输出

兑换的最小货币个数是4

总结

核心是填表,填表又依赖于子问题之间的递推关系。原问题描述本身是重要的,直接导致子问题的描述,以及递推关系的建立。只要递推关系建立好了,填表就自底向上按部就班完成就好了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值