zoukankan      html  css  js  c++  java
  • [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        1         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       1       2      3      4      5      6      7      8      10


    最后注意:输出范围超出了32位。


    一维code:

    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了很多次

    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;
    }

            

  • 相关阅读:
    CodeForces
    网络流
    poj 2185
    树的分治学习
    数位DP
    URAL 1969. Hong Kong Tram
    hdu 4759 Poker Shuffle
    hdu3712 Detector Placement
    分块思想
    莫比乌斯反演
  • 原文地址:https://www.cnblogs.com/freezhan/p/3219067.html
Copyright © 2011-2022 走看看