【动态规划】钱币问题

题目描述

某个国家的货币系统中,有 m 种面额的钱币,现在要用这 m 种面额的钱币凑足 n 元钱,问一共有多少种方法。m 种钱币不一定要都被用到。

例如,有 3 种钱币,分别为1、2、5,那么有四种方法拼凑出5元钱

  1. (1,1,1,1,1) 全是1元钱
  2. (1,2,2)(1,1,1,2) 使用1元和2元
  3. (5) 只用5元钱

注意:方案中的钱币不分顺序,也就是说(1,2,2) 和(2,1,2)是同一种方法。


输入

输入两个数字 m, n(1≤m≤20,200≤n≤10000),第二行 m 个数字,代表 m 种钱币的面额,钱币面额大于0,数据中保证 m 种钱币各不相同。

输出

输出一个整数,代表拼凑出 n 元钱的方法数,答案有可能过大,请对 9973 取余。


样例输入1
8 200
1 2 5 10 20 50 100 200
样例输出1
3871

数据规模与约定

时间限制:1 s

内存限制:64 M

100% 的数据保证 1≤m≤20,200≤n≤10000


容斥原理应用

我们不妨定义f[i][j],表示用前i种面值组成j的方法总数。用容斥原理理解,我们可以将f[i][j]分为相斥的两部分:由包含第i种面值的钞票的方法数+不包含第i种面值的钞票的方法数组成。

我们定义数组cash[i]来存储i处的钞票面值。

不包含第i种面值的钞票的方法数可以用f[i-1][j]来表示。而包含第i种面值的钞票的方法数可以用f[i][j-cash[i]]来表示。

理解f[i][j-cash[i]]:我们默认已经使用了一张cash[i]。仅讨论使用了一张cash[i]之后的方法数。这样我们可以发现f[i][0]应当为1,表示一张cash[i]就能组成所需面值的情况,即只需一种方案。

于是我们可以用代码表示,当j小于cash[i]时,显然用不上cash[i],f[i][j]即等于f[i-1][j];

当j大于等于cash[i]时,可以将包含第i种面值的钞票的方法数f[i][j-cash[i]]加上f[i-1][j]表示。

代码表示如下

for (int i = 1; i <=m; i++) {
	f[i][0] = 1;
	for (int j = 1; j <= n; j++) {
		f[i][j] = f[i - 1][j];
		if (j < cash[i])continue;
		f[i][j] += f[i][j - cash[i]];
		f[i][j] %= 9973;//不要忘记题目对于大数字的取余要求的实现
	}
}

完整代码:

#include <iostream>
#define MAX_N 10000
#define MAX_M 20
using namespace std;
int cash[MAX_M+5];
int f[MAX_M+5][MAX_N+5];
int main() {
	int m, n;
	cin >> m >> n;
	for (int i = 1; i <= m; i++) {
		cin >> cash[i];
	}
	for (int i = 1; i <=m; i++) {
		f[i][0] = 1;
		for (int j = 1; j <= n; j++) {
			f[i][j] = f[i - 1][j];
			if (j < cash[i])continue;
			f[i][j] += f[i][j - cash[i]];
			f[i][j] %= 9973;
		}
	}
	cout << f[m][n] << endl;
	return 0;
}

  • 14
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值