题目描述
In the United Kingdom the currency is made up of pound (£) and pence §. There are eight coins in general circulation:
1p, 2p, 5p, 10p, 20p, 50p, £1 (100p), and £2 (200p).
It is possible to make £2 in the following way:
1×£1 + 1×50p + 2×20p + 1×5p + 1×2p + 3×1p
How many different ways can £2 be made using any number of coins?
题目分析1
f(n,m)表示前n中钱币拼凑m元钱的方法总数 这种问题就是递推问题
对于递推问题我们首先需要推导他的公式
对于这道题,是否包含了第n种钱币(即前n-1种就可以拼凑出m元钱)是一个关键点
所以
f
(
n
,
m
)
=
f
(
n
−
1
,
m
)
+
f
(
n
,
m
−
w
[
n
]
)
(
w
[
n
]
表
示
第
n
中
钱
币
的
面
值
)
f(n,m) = f(n-1,m) + f(n,m-w[n])(w[n]表示第n中钱币的面值)
f(n,m)=f(n−1,m)+f(n,m−w[n])(w[n]表示第n中钱币的面值)
将前n种钱币凑成m元钱拆成前n-1种钱币凑m元钱和一定用到第n种钱币的方法的加和
这两部分是两两相互独立的,所以满足等式,若两者不是相互独立的,我们需要用容斥原理将公共部分减掉
通过上述公式,要想知道f(n,m)必须先知道f(n-1,m)和f(n,m-w[n]),所以需要不断的向前递推
题目代码1
//递推
#include<stdio.h>
#define max_n 8
#define max_m 200
int w[max_n + 5] = {
1, 2, 5, 10, 20, 50, 100, 200
};
int f[max_n + 5][max_m + 5];
int main(){
for(int i = 0; i < max_n; i++){
f[i][0] = 1;//递推的初始条件 用前i种货币凑0元的方法为一种
for(int j = 1; j <= max_m; j++){
//f[i][j] = 0;
if(i >= 1) f[i][j] += f[i - 1][j]; //f[0][j]全部为1 即用1元生成j的方法
if(j >= w[i]) f[i][j] += f[i][j - w[i]];//用1 5块的生成2块 显然5不可能用得到
}
}
printf("%d\n", f[max_n - 1][max_m]);
return 0;
}
题目分析2
我们接着来看公式
f
(
n
,
m
)
=
f
(
n
−
1
,
m
)
+
f
(
n
,
m
−
w
[
n
]
)
(
w
[
n
]
表
示
第
n
中
钱
币
的
面
值
)
f(n,m) = f(n-1,m) + f(n,m-w[n])(w[n]表示第n中钱币的面值)
f(n,m)=f(n−1,m)+f(n,m−w[n])(w[n]表示第n中钱币的面值)
我们可以看到当计算f(n,m)时,我们只需要上一行的同列数据和这一行前面的某个数据即可,所以我们可以将数组的行数减少,我们只需要两行数组即可,采用滚动数组的思想
题目代码2
//递推 滚动数组
#include<stdio.h>
#define max_n 8
#define max_m 200
int w[max_n + 5] = {
1, 2, 5, 10, 20, 50, 100, 200
};
int f[2][max_m + 5]; //公式f(n,m) = f(n-1,m) + f(n,m-w[n]) f(n,m)只需要上一行的同一列值和这一行前面某列的值,所以只需要两行的值
int main(){
for(int k = 0; k < max_n; k++){
int i = k % 2;//i指针用来滚动数组0->0 1->1 2->0 3->1 4->0 5->1 6->0 7->1
f[i][0] = 1;
for(int j = 1; j <= max_m; j++){
f[i][j] = 0;//这里记得必须要清零,不然会在原有数的基础上继续+
f[i][j] += f[i ^ 1][j];//数组只有两行,按照公式,f[i][j] 的第一部分f[i - 1][j]肯定在另一行
if(j >= w[k]) f[i][j] += f[i][j - w[k]];//如果2块一定要用5块来凑,则不存在
}
}
printf("%d\n", f[(max_n - 1) % 2][max_m]);
return 0;
}
题目分析3
由上图我们可以分析出只需要一维的数组即可,实现降为f[j]重新刷新i次,表示用前i种货币凑j元钱,只要在原有的基础上加上f(j-w[i])即可实现f(j)的计算。
当数组刷新了i次时,此时f(j)表示f(i,j),当进行第i+1次刷新时,在原有f(j)的基础上加上前面的f(j - w[i + 1])(此时的f(j-w[i+1])已经刷新为f(i+1,j-w[i+1]))即可得到f(i+1,j)
降维既省空间又省时间
题目代码3
#include<stdio.h>
#define max_n 8
#define max_m 200
int w[max_n] = {
1, 2, 5, 10, 20, 50, 100, 200
};
int f[max_m + 5] = {0};
int main(){
for(int i = 0; i < max_n; i++){
f[0] = 1;
for(int j = 1; j <= max_m; j++){
if(j >= w[i]) f[j] += f[j - w[i]];
}
}
printf("%d\n", f[max_m]);
return 0;
}