题目点我
本来是道简单的题目,主要想写下优化的过程。
给你无限多面值为1,5,10,25,50的硬币,问你用他们组成价值为
M
的组合方法数是多少。用
Fk(v)=∑i=0i∗coin[k]≤vFk−1(v−i∗coin[k])
直接按照递推方程写代码的话要三重循环:分别枚举 k,v,i ,时间复杂度 O(KVI) ,空间复杂度 O(KV) 。但是可以利用计算顺序来优化时空复杂度。看下面的矩阵:
F1(0)F2(0)⋮Fk−1(0)Fk(0)F1(1)F2(1)⋮Fk−1(1)Fk(1)⋯⋯⋱⋯⋯F1(v−1)F2(v−1)⋮Fk−1(v−1)Fk(v−1)F1(v)F2(v)⋮Fk−1(v)Fk(v)⋯⋯⋯⋯⋯
计算 Fk(v) 时需要上一行,即 Fk−1(v′),v′≤v 的所有值,所以计算顺序应该是左上到右下,即三重循环都是递增的。
把一开始的递推方程变型,得到
Fk(v)=Fk−1(v)+Fk−1(v−coin[k])+Fk−1(v−2∗coin[k])+⋯+Fk−1(v−n∗coin[k])=Fk−1(v)+Fk(v−coin[k])
由于计算顺序,等式右边两个值都已经得到了,这样就省去了枚举硬币个数的循环,时间复杂度降到 O(KV) 。
而此时, Fk(v) 的值还没有动过,如果省去下标 k ,用一维数组存储结果,即
相当于每次更新 F(v) 的值,就是用 k 个硬币的方案数将原来
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 30100
int coin[5] = {1, 5, 10, 25, 50};
long long dp[MAX];
void solve(int total_sum){
int i;
for(i = 1; i < 5; i++){
for(int sum = coin[i]; sum <= total_sum; sum++){
dp[sum] += dp[sum - coin[i]];
}
}
}
int main(){
memset(dp, 0, sizeof(dp));
for(int sum = 0; sum <= MAX; sum++)
dp[sum] = 1;
int total_sum;
solve(MAX);
long long ans;
while(scanf("%d", &total_sum) != EOF){
ans = dp[total_sum];
if(ans == 1)
printf("There is only 1 way to produce %d cents change.\n", total_sum);
else
printf("There are %lld ways to produce %d cents change.\n", ans, total_sum);
}
return 0;
}