拼凑钱币
给你六种面额 1、5、10、20、50、100 元的纸币,假设每种币值的数量都足够多,编写程序求组成N元(N为0~10000的非负整数)的不同组合的个数。
输入描述:
输入包括一个整数n(1 ≤ n ≤ 10000)
输出描述:
输出一个整数,表示不同的组合方案数
示例1
输入
1
输出
1
sum = x1 * V1 + x2 * V2 + ... + xm * Vm
求所有可能的组合数,就是求满足前面等值的系数{x1, x2, ..., xm}的所有可能个数。
我们希望用m种硬币构成sum,根据最后一个硬币Vm的系数的取值为无非有这么几种情况,xm分别取{0, 1, 2, ..., sum/Vm},换句话说,上面分析中的等式和下面的几个等式的联合是等价的。
sum = x1 * V1 + x2 * V2 + ... + 0 * Vm
sum = x1 * V1 + x2 * V2 + ... + 1 * Vm
sum = x1 * V1 + x2 * V2 + ... + 2 * Vm
...
sum = x1 * V1 + x2 * V2 + ... + K * Vm
其中K是该xm能取的最大数值K = sum / Vm。可是这又有什么用呢?不要急,我们先进行如下变量的定义:
dp[i][sum] = 用前i种硬币构成sum 的所有组合数。
那么题目的问题实际上就是求dp[m][sum],即用前m种硬币(所有硬币)构成sum的所有组合数。在上面的联合等式中:当xn=0时,有多少种组合呢? 实际上就是前i-1种硬币组合sum,有dp[i-1][sum]种! xn = 1 时呢,有多少种组合? 实际上是用前i-1种硬币组合成(sum - Vm)的组合数,有dp[i-1][sum -Vm]种; xn =2呢, dp[i-1][sum - 2 * Vm]种,等等。所有的这些情况加起来就是我们的dp[i][sum]。所以:
dp[i][sum] = dp[i-1][sum - 0*Vm] + dp[i-1][sum - 1*Vm]
+ dp[i-1][sum - 2*Vm] + ... + dp[i-1][sum - K*Vm]; 其中K = sum / Vm
归纳成数学表达式即为:
代码:
#include<iostream>
#include<vector>
using namespace std;
const int tab[6] = { 1,5,10,20,50,100 };
int main() {
long long cnt = 0;
long long n;
while (cin >> n) {
vector<vector<long long> > dp(6, vector<long long>(n + 1, 0));
for (int i = 0; i<n + 1; i++)
dp[0][i] = 1;
for (int i = 1; i<6; i++) {
for (int sum = 1; sum <= n; sum++) {
for (int k = sum / tab[i]; k >= 0; k--) {
dp[i][sum] = dp[i][sum] + dp[i - 1][sum - k*tab[i]];
}
}
}
cout << dp[5][n] << endl;
}
}
其实:dp[i][sum]=dp[i-1][sum]+dp[i][sum-Vm],进一步可以简化,用一维数组代替二维矩阵
代码:
#include<iostream>
#include<vector>
using namespace std;
const int tab[6]={1,5,10,20,50,100};
int main(){
long long int cnt=0;
long long int n;
while(cin>>n){
vector<long long int> dp(n+1,1);
for(int i=1;i<6;i++){
for(int sum=tab[i];sum<=n;sum++){
dp[sum]=dp[sum]+dp[sum-tab[i]];
}
}
cout<<dp[n]<<endl;
}
}