[USACO 2.3.4]货币系统【DP完全背包的简单应用】CSUST 1081

CSUST 1081

1081: [USACO 2.3.4]货币系统

Time Limit: 1 Sec   Memory Limit: 64 MB
Submit: 142   Solved: 54

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



算法: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:

Accepted
992
0
C++/Edit628 B2013-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了很多次

Accepted
3260
3
C++/Edit871 B2013-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;
}

        

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值