题目描述
某个国家的货币系统中,有 m 种面额的钱币,现在要用这 m 种面额的钱币凑足 n 元钱,问一共有多少种方法。m 种钱币不一定要都被用到。
例如,有 3 种钱币,分别为1、2、5,那么有四种方法拼凑出5元钱
(1,1,1,1,1)
全是1元钱(1,2,2)
,(1,1,1,2)
使用1元和2元(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;
}