UVA 147 Dollars (完全背包)
http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=83
题意:
有面值为$100, $50, $20, $10, $5 ,$2, $1, 50c, 20c,10c,5c的11种硬币,每种硬币可以无限使用.现在给你任意一个钱数目x(x<=300.00$), 问你用上面的11种硬币有多少种方法能构成x $ ?
分析:
由于每种硬币的数目都是无限的, 所以本题可以看成是一道完全背包问题.
首先我们必须将double美元变成整数美分. 有下面两种方法:
1. 直接让double*100转成int美分即可,不过要+0.5修真值.
2. 用scanf("%d.%d",&m1,&m2)来读,美分数量==m1*100+m2.
下面就是处理完全背包的过程了:
我们令val[i]表示第i种硬币的面值, 令dp[i][j]==x 表示由前i种硬币构成面值j美分有x种方式.
初值dp为0且dp[0][0]=1. 状态方程为:
dp[i][j] = sum( dp[i-1][j] , dp[i][j-val[i]] ) //sum为求和
最终我们所求为dp[n][x]的值.
如何理解上述的状态转移方程呢?
首先来看dp[i][j], 它表示用前i中硬币构成j美分的方法数目. 那么:
1. 如果我们根本不用第i种硬币(只用前i-1种硬币即可),我们可以知道有dp[i-1][j]种方式可以(不用任何一个第i种硬币)构成j美分.
2. 如果我们至少用1个第i种硬币来构成j美分, 那么我们可以知道有dp[i][j-val[i]] 种方法能达成目的.
综上所述, dp[i][j]==用前i种硬币构成j美分的方法总数== sum( dp[i-1][j] , dp[i][j-val[i]] ).
程序实现用的滚动数组, 所以dp只有[j]这维. 且注意j必须从小到大循环了.(想想为什么)
AC代码:
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxn=30000+5; int n=11;//硬币种数 int val[]={5,10,20,50,100,200,500,1000,2000,5000,10000}; long long dp[maxn]; int main() { //初始化 memset(dp,0,sizeof(dp)); dp[0]=1; //递推过程 for(int i=0;i<n;i++) for(int j=val[i];j<maxn;j++) dp[j] += dp[j-val[i]]; //输出结果 double money; while(scanf("%lf",&money)==1 && money>0) { //double读入有精度误差,所以+0.5 //也可以scanf("%d.%d",&m1,&m2);这样来读money int m=(int)(money*100+0.5); printf("%6.2lf%17lld\n",money,dp[m]); } return 0; }