CSUST 1081
1081: [USACO 2.3.4]货币系统
Time Limit: 1 Sec
Memory Limit: 64 MB
Submit: 142
Solved: 54
Submit
Status
Web Board
Description
母牛们不但创建了他们自己的政府而且选择了建立了自己的货币系统。 [In their own rebellious way],,他们对货币的数值感到好奇。传统地,一个货币系统是由1,5,10,20 或 25,50, 和 100的单位面值组成的。母牛想知道有多少种不同的方法来用货币系统中的货币来构造一个确定的数值。举例来说, 使用一个货币系统 {1,2,5,10,...}产生 18单位面值的一些可能的方法是:18x1, 9x2, 8x2+2x1, 3x5+2+1,等等其它。写一个程序来计算有多少种方法用给定的货币系统来构造一定数量的面值。保证总数将会适合long long (C/C++) 和 Int64 (Free Pascal)。
Input
货币系统中货币的种类数目是 V 。 (1<= V<=25) 要构造的数量钱是 N 。 (1<= N<=10,000) 第 1 行: 二整数, V 和 N 第 2 ..V+1行: 可用的货币 V 个整数 (每行一个 每行没有其它的数)。
Output
Sample Input
3 101 2 5
Sample Output
10
HINT
Source
Submit
Status
Web Board
算法:DP
总结:和完全背包问题很像,每种硬币可以选择无限次,问装满背包的方法。
最终事实证明,只用把完全背包的模板的 max 改成 sum 就可以了,比赛时居然没有做出,还一直在那儿划分啊划分。
思路:
二维:用dp[i][j]表示用前面 i 种货币可以表示总货币 j 的方法数
状态转移方程:
dp[i][j] = dp[i-1][j] + dp[i][j-a[i]];
表示前面 i 种货币可以表示总货币 j 的方法数 =
不用第 i 种货币(只用 0 到 i-1 种)可以表示总货币 j 的方法数 +
用前 i 种货币可以表示总货币 j-a[i]的方法数。
伪代码:
先将货币从小到大排序。
货币: i: 0 . . . V-1
要构成的总货币:j: a[i] . . . N
dp[i][j] = dp[i-1][j] + dp[i][j-a[i]];
注意:初始化dp[0][0] = 0;
每次选入货币的时候要把比 a[i]少的先存入 dp[i]
(也就是对于总钱数少于 a[i]的直接存入 dp[i]=dp[i-1]),具体看代码和下面的分析。
用样例来举例:
3 10
1 2 5
dp[0][1],dp[0][2],dp[0,3], [4], [5], [6], [7], [8], [9], [10]
1 1 1 1 1 1 1 1 1 1 1
2 2 2 3 3 4 4 5 5 6
5 4 5 6 7 8 10
结果 1 2 2 3 4 5 6 7 8 10
注意到:1.没有更新的继续继承上一层的结果。
2.每次算到 dp[i][j] = dp[i-1][j] + dp[i][j-a[i]] 的时候,
dp[i][j-a[i]]已经由dp[i-1][j-a[i]]更新成功,
所以这样就证明了只用一维数组dp[i]保存构成总货币为 i 的种数即可,
这也是开始看《背包九讲》时迷 糊的地方,好吧传说中的滚动数组,自己遍历了一遍,总算有所了解了
一维 :
伪代码:
a[i] 按照从小到大排序,初始化 dp[0] = 0;
i: 0 . . . V
j: a[i] . . .n
dp[j] += dp[j-a[i]];
一维不仅节约了内存还免去了每次存入不能更新的种数的麻烦。
dp[1], dp[2], dp[3], dp[4], dp[5], dp[6], dp[7], dp[8], dp[9], dp[10]
1 1 1 1 1 1 1 1 1 1 1
2 2 2 3 3 4 4 5 5 6
5 4 5 6 7 8 10
结果 1 2 2 3 4 5 6 7 8 10
最后注意:输出范围超出了32位。
一维code:
1081
|
Accepted |
992
|
0
|
C++/Edit |
628 B |
2013-05-03 16:57:38 |
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
long long dp[10005];
int a[30];
int main()
{
int v, n;
while(scanf("%d%d", &v, &n) != EOF)
{
for(int i = 0; i < v; i++)
{
scanf("%d", &a[i]);
}
sort(a, a+v);
memset(dp, 0, sizeof(dp));
dp[0] = 1;
for(int i = 0; i < v; i++)
{
for(int j = a[i]; j <= n; j++)
dp[j] += dp[j-a[i]];
}
printf("%lld\n", dp[n]);
}
return 0;
}
二维code:
教训:来自KB神,不要舍不得用内存,RuntimeError了很多次
1081
|
Accepted |
3260
|
3
|
C++/Edit |
871 B |
2013-05-03 17:24:51 |
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
long long dp[30][10010]; //内存开大点
int a[30];
int main()
{
int v, n;
while(scanf("%d%d", &v, &n) != -1)
{
for(int i = 0; i < v; i++)
{
scanf("%d", &a[i]);
}
sort(a, a+v);
dp[0][0] = 1; //dp[i][j]选到第 i 种钱时,组成 j 的总类
for(int j = a[0]; j <= n; j++) //先预处理好只选最小的货币的情况
dp[0][j] = dp[0][j-a[0]];
for(int i = 1; i < v; i++)
{
for(int j = 0; j < a[i]; j++) //注意:存放不能更新的
dp[i][j] = dp[i-1][j];
for(int j = a[i]; j <= n; j++)
{
dp[i][j] = dp[i-1][j] + dp[i][j-a[i]];
}
}
printf("%lld\n", dp[v-1][n]);
}
return 0;
}