一、题意
给定金额n,有50, 25, 10, 5, 1这五种面值的钱,问共有多少种不同的找法。
二、解析
看到这道题,第一反应是用DAG的记忆化搜索法求解,即每个金额V指向V-coins[i]。但这样会出现一个问题,就是重复计数的问题。比如16块钱,10->5->1的找法和1->5->10的找***被当作不同的找法,这是不对的。如果用递推法中的填表法也是一样,无法避免重复计算的问题。
这个时候就得使用递推法中的“刷表法”了。
即用每一种面值的纸币coins[i]去分别“更新”每一种金额j的找法数量dp[j]。
比如,当只有10这个面值时,找10块钱金额只有 10 一种找法,当新增一种面值5时,找法数量就在原来的基础上新增了包含5的这一部分,即5->5这种找法。通过这样刷表,就不出现重复了。
三、代码
#include <iostream> using namespace std; const int N = 10000; const int coins[5] = {50, 25, 10, 5, 1}; int dp[N]; int main() { fill(dp, dp + N, 0); dp[0] = 1; for(int i = 0; i < 5; i ++) { // 每次增加一种面值进行刷表法 for(int j = coins[i]; j < N; j ++) { // 每一种金额利用新面值都会新增一些凑法,无需考虑数量 dp[j] += dp[j - coins[i]]; } } int num; while(cin >> num) cout << dp[num] << endl; }
四、归纳
- 填表法:对于每一种状态i,更新dp[i]
- 刷表法:对于每一种状态i,更新dp[i]
- 当填表法难以实现时,想想看能不能用刷表法
- 计数类的问题时,注意会不会出现重复问题
- 有时候可以在刷表/填报完成后,再进行输入和输出,即预先算好所有答案值,以加快速度