zoukankan      html  css  js  c++  java
  • poj 1742 Coins(dp之多重背包+多次优化)

    Description

    People in Silverland use coins.They have coins of value A1,A2,A3...An Silverland dollar.One day Tony opened his money-box and found there were some coins.He decided to buy a very nice watch in a nearby shop. He wanted to pay the exact price(without change) and he known the price would not more than m.But he didn't know the exact price of the watch. 
    You are to write a program which reads n,m,A1,A2,A3...An and C1,C2,C3...Cn corresponding to the number of Tony's coins of value A1,A2,A3...An then calculate how many prices(form 1 to m) Tony can pay use these coins. 

    Input

    The input contains several test cases. The first line of each test case contains two integers n(1<=n<=100),m(m<=100000).The second line contains 2n integers, denoting A1,A2,A3...An,C1,C2,C3...Cn (1<=Ai<=100000,1<=Ci<=1000). The last test case is followed by two zeros.

    Output

    For each test case output the answer on a single line.

    Sample Input

    3 10
    1 2 4 2 1 1
    2 5
    1 4 2 1
    0 0

    Sample Output

    8
    4

    Source

     

    传说中的男人八题,是男人就A这八题。有n种面额的硬币,面额个数分别为A_i、C_i,求最多能搭配出几种不超过m的金额?

    这是一个多重部分和问题(多重背包问题),放在了《2.3 记录结果再利用的“动态规划” 优化递推关系式》。最基本的做法是:

    dp[i][j] := 用前i种硬币能否凑成j

    递推关系式:

    dp[i][j] = (存在k使得dp[i – 1][j – k * A[i]]为真,0 <= k <= m 且下标合法)

    然后三重循环ijk递推

     1 #include <iostream>
     2 #include <algorithm>
     3 using namespace std;
     4  
     5 bool dp[100 + 16][100000 + 16]; // dp[i][j] := 用前i种硬币能否凑成j
     6 int A[100 + 16];
     7 int C[100 + 16];
     8  
     9 ///////////////////////////SubMain//////////////////////////////////
    10 int main(int argc, char *argv[])
    11 {
    12 #ifndef ONLINE_JUDGE
    13     freopen("in.txt", "r", stdin);
    14     freopen("out.txt", "w", stdout);
    15 #endif
    16     int n, m;
    17     while(cin >> n >> m && n > 0)
    18     {
    19         memset(dp, 0, sizeof(dp));
    20         for (int i = 0; i < n; ++i)
    21         {
    22             cin >> A[i];
    23         }
    24         for (int i = 0; i < n; ++i)
    25         {
    26             cin >> C[i];
    27         }
    28         dp[0][0] = true;
    29         for (int i = 0; i < n; ++i)
    30         {
    31             for (int j = 0; j <= m; ++j)
    32             {
    33                 for (int k = 0; k <= C[i] && k * A[i] <= j; ++k)
    34                 {
    35                     dp[i + 1][j] |= dp[i][j - k * A[i]];
    36                 }
    37             }
    38         }
    39         int answer = count(dp[n] + 1, dp[n] + 1 + m , true); // 总额0不算在答案内
    40         cout << answer << endl;
    41     }
    42 #ifndef ONLINE_JUDGE
    43     fclose(stdin);
    44     fclose(stdout);
    45     system("out.txt");
    46 #endif
    47     return 0;
    48 }
    49 ///////////////////////////End Sub//////////////////////////////////
    View Code

    这种代码不用提交也知道会TLE,因为这个朴素的算法的复杂度是O(m∑iCi),比如那第二个用例画成图的话会看到:

    解释一下,dp数组和更新顺序为:

    第二个用例:
    1 0 0 0 0 0 
    0 0 0 0 0 0 
    0 0 0 0 0 0 
    ————–
    因为可以用0个1加上dp[0][0]拼成0;所以,dp[1][0]被更新为真
    因为可以用1个1加上dp[0][0]拼成1;所以,dp[1][1]被更新为真
    因为可以用2个1加上dp[0][0]拼成2;所以,dp[1][2]被更新为真
    1 0 0 0 0 0 
    1 1 1 0 0 0 
    0 0 0 0 0 0 
    ————–
    因为可以用0个4加上dp[1][0]拼成0;所以,dp[2][0]被更新为真
    因为可以用0个4加上dp[1][1]拼成1;所以,dp[2][1]被更新为真
    因为可以用0个4加上dp[1][2]拼成2;所以,dp[2][2]被更新为真
    因为可以用1个4加上dp[1][0]拼成4;所以,dp[2][4]被更新为真
    因为可以用1个4加上dp[1][1]拼成5;所以,dp[2][5]被更新为真
    1 0 0 0 0 0 
    1 1 1 0 0 0 
    1 1 1 0 1 1 
    ————–
    4
    顺便第一个用例:
    1 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 
    ————–
    因为可以用0个1加上dp[0][0]拼成0;所以,dp[1][0]被更新为真
    因为可以用1个1加上dp[0][0]拼成1;所以,dp[1][1]被更新为真
    因为可以用2个1加上dp[0][0]拼成2;所以,dp[1][2]被更新为真
    1 0 0 0 0 0 0 0 0 0 0 
    1 1 1 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 
    ————–
    因为可以用0个2加上dp[1][0]拼成0;所以,dp[2][0]被更新为真
    因为可以用0个2加上dp[1][1]拼成1;所以,dp[2][1]被更新为真
    因为可以用0个2加上dp[1][2]拼成2;所以,dp[2][2]被更新为真
    因为可以用1个2加上dp[1][1]拼成3;所以,dp[2][3]被更新为真
    因为可以用1个2加上dp[1][2]拼成4;所以,dp[2][4]被更新为真
    1 0 0 0 0 0 0 0 0 0 0 
    1 1 1 0 0 0 0 0 0 0 0 
    1 1 1 1 1 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 
    ————–
    因为可以用0个4加上dp[2][0]拼成0;所以,dp[3][0]被更新为真
    因为可以用0个4加上dp[2][1]拼成1;所以,dp[3][1]被更新为真
    因为可以用0个4加上dp[2][2]拼成2;所以,dp[3][2]被更新为真
    因为可以用0个4加上dp[2][3]拼成3;所以,dp[3][3]被更新为真
    因为可以用0个4加上dp[2][4]拼成4;所以,dp[3][4]被更新为真
    因为可以用1个4加上dp[2][1]拼成5;所以,dp[3][5]被更新为真
    因为可以用1个4加上dp[2][2]拼成6;所以,dp[3][6]被更新为真
    因为可以用1个4加上dp[2][3]拼成7;所以,dp[3][7]被更新为真
    因为可以用1个4加上dp[2][4]拼成8;所以,dp[3][8]被更新为真
    1 0 0 0 0 0 0 0 0 0 0 
    1 1 1 0 0 0 0 0 0 0 0 
    1 1 1 1 1 0 0 0 0 0 0 
    1 1 1 1 1 1 1 1 1 0 0 
    ————–
    8

    这个算法每次只记录一个bool值,损失了不少信息。在这个问题中,不光能够求出是否能得到某个金额,同时还能把得出了此金额时A_i还剩下多少个算出来,这样直接省掉了k那重循环。

    优化dp定义:

    dp[i][j] := 用前i种硬币凑成j时第i种硬币最多能剩余多少个(-1表示配不出来)
                如果dp[i - 1][j] >= 0(前i-1个数可以凑出j,那么第i个数根本用不着)直接为C[i]
    dp[i][j] =  如果j < A[i]或者dp[i][j - a[i]] <=0 (面额太大或者在配更小的数的时候就用光了)-1
                其他(将第i个数用掉一个) dp[i][j-a[i]] - 1

    最后统计一下dp数组第n行>=0的个数就知道答案了:

     1 #include <iostream>
     2 #include <algorithm>
     3 using namespace std;
     4  
     5 int dp[100 + 16][100000 + 16]; // dp[i][j] := 用前i种硬币凑成j时第i种硬币最多能剩余多少个
     6 int A[100 + 16];
     7 int C[100 + 16];
     8  
     9 ///////////////////////////SubMain//////////////////////////////////
    10 int main(int argc, char *argv[])
    11 {
    12 #ifndef ONLINE_JUDGE
    13     freopen("in.txt", "r", stdin);
    14     freopen("out.txt", "w", stdout);
    15 #endif
    16     int n, m;
    17     while(cin >> n >> m && n > 0)
    18     {
    19         memset(dp, -1, sizeof(dp));
    20         dp[0][0] = 0;
    21         for (int i = 0; i < n; ++i)
    22         {
    23             cin >> A[i];
    24         }
    25         for (int i = 0; i < n; ++i)
    26         {
    27             cin >> C[i];
    28         }
    29         for (int i = 0; i < n; ++i)
    30         {
    31             for (int j = 0; j <= m; ++j)
    32             {
    33                 if (dp[i][j] >= 0)
    34                 {
    35                     dp[i + 1][j] = C[i];
    36                 }
    37                 else if (j < A[i]                        // 用一个就超出,不能用
    38                         || dp[i + 1][j - A[i]] <= 0)      // 连凑比j小的数的时候都用完了,此时更加用完了
    39                 {
    40                     dp[i + 1][j] = -1;
    41                 }
    42                 else
    43                 {
    44                     dp[i + 1][j] = dp[i + 1][j - A[i]] - 1;       // 用上了一个第i个硬币
    45                 }
    46             }
    47         }
    48         int answer = count_if(dp[n] + 1, dp[n] + 1 + m , bind2nd(greater_equal<int>(), 0)); // 总额0不算在答案内
    49         cout << answer << endl;
    50     }
    51 #ifndef ONLINE_JUDGE
    52     fclose(stdin);
    53     fclose(stdout);
    54     system("out.txt");
    55 #endif
    56     return 0;
    57 }
    58 ///////////////////////////End Sub//////////////////////////////////
    View Code

    还是拿第二个用例画个图:

    第二个用例:
    0 -1 -1 -1 -1 -1 
    -1 -1 -1 -1 -1 -1 
    -1 -1 -1 -1 -1 -1 
    ————–
    dp[0][0]不为负,dp[1][0]更新为硬币0的个数。
    dp[0][1]其他,dp[1][1]更新为dp[1][0]
    dp[0][2]其他,dp[1][2]更新为dp[1][1]
    dp[1][2]用完了,dp[1][3]更新为硬币0的个数。
    dp[1][3]用完了,dp[1][4]更新为硬币0的个数。
    dp[1][4]用完了,dp[1][5]更新为硬币0的个数。
    0 -1 -1 -1 -1 -1 
    2 1 0 -1 -1 -1 
    -1 -1 -1 -1 -1 -1 
    ————–
    dp[1][0]不为负,dp[2][0]更新为硬币1的个数。
    dp[1][1]不为负,dp[2][1]更新为硬币1的个数。
    dp[1][2]不为负,dp[2][2]更新为硬币1的个数。
    余额太大,dp[2][3]更新为硬币1的个数。
    dp[1][4]其他,dp[2][4]更新为dp[2][0]
    dp[1][5]其他,dp[2][5]更新为dp[2][1]
    0 -1 -1 -1 -1 -1 
    2 1 0 -1 -1 -1 
    1 1 1 -1 0 0 
    ————–
    4
    第一个用例:
    0 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 
    -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 
    -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 
    -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 
    ————–
    dp[0][0]不为负,dp[1][0]更新为硬币0的个数。
    dp[0][1]其他,dp[1][1]更新为dp[1][0]
    dp[0][2]其他,dp[1][2]更新为dp[1][1]
    dp[1][2]用完了,dp[1][3]更新为硬币0的个数。
    dp[1][3]用完了,dp[1][4]更新为硬币0的个数。
    dp[1][4]用完了,dp[1][5]更新为硬币0的个数。
    dp[1][5]用完了,dp[1][6]更新为硬币0的个数。
    dp[1][6]用完了,dp[1][7]更新为硬币0的个数。
    dp[1][7]用完了,dp[1][8]更新为硬币0的个数。
    dp[1][8]用完了,dp[1][9]更新为硬币0的个数。
    dp[1][9]用完了,dp[1][10]更新为硬币0的个数。
    0 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 
    2 1 0 -1 -1 -1 -1 -1 -1 -1 -1 
    -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 
    -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 
    ————–
    dp[1][0]不为负,dp[2][0]更新为硬币1的个数。
    dp[1][1]不为负,dp[2][1]更新为硬币1的个数。
    dp[1][2]不为负,dp[2][2]更新为硬币1的个数。
    dp[1][3]其他,dp[2][3]更新为dp[2][1]
    dp[1][4]其他,dp[2][4]更新为dp[2][2]
    dp[2][3]用完了,dp[2][5]更新为硬币1的个数。
    dp[2][4]用完了,dp[2][6]更新为硬币1的个数。
    dp[2][5]用完了,dp[2][7]更新为硬币1的个数。
    dp[2][6]用完了,dp[2][8]更新为硬币1的个数。
    dp[2][7]用完了,dp[2][9]更新为硬币1的个数。
    dp[2][8]用完了,dp[2][10]更新为硬币1的个数。
    0 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 
    2 1 0 -1 -1 -1 -1 -1 -1 -1 -1 
    1 1 1 0 0 -1 -1 -1 -1 -1 -1 
    -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 
    ————–
    dp[2][0]不为负,dp[3][0]更新为硬币2的个数。
    dp[2][1]不为负,dp[3][1]更新为硬币2的个数。
    dp[2][2]不为负,dp[3][2]更新为硬币2的个数。
    dp[2][3]不为负,dp[3][3]更新为硬币2的个数。
    dp[2][4]不为负,dp[3][4]更新为硬币2的个数。
    dp[2][5]其他,dp[3][5]更新为dp[3][1]
    dp[2][6]其他,dp[3][6]更新为dp[3][2]
    dp[2][7]其他,dp[3][7]更新为dp[3][3]
    dp[2][8]其他,dp[3][8]更新为dp[3][4]
    dp[3][5]用完了,dp[3][9]更新为硬币2的个数。
    dp[3][6]用完了,dp[3][10]更新为硬币2的个数。
    0 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 
    2 1 0 -1 -1 -1 -1 -1 -1 -1 -1 
    1 1 1 0 0 -1 -1 -1 -1 -1 -1 
    1 1 1 1 1 0 0 0 0 -1 -1 
    ————–
    8

    本以为这次照着书上的思路来的应该没问题了吧,数组再利用就懒得做了。于是提交,结果MLE

    于是打起精神来重复利用数组,注意到上图中的箭头都是垂直的,也就是说可以定义

    dp[j] := 在第i次循环时之前表示用前i-1种硬币凑成j时第i种硬币最多能剩余多少个(-1表示配不出来),循环之后就表示第i次的状态

    于是就省了一维数组:

     1 #include <iostream>
     2 #include <set>
     3 #include <algorithm>
     4 using namespace std;
     5  
     6 int dp[100000 + 16]; // dp[i][j] := 用前i种硬币凑成j时第i种硬币最多能剩余多少个
     7 int A[100 + 16];
     8 int C[100 + 16];
     9  
    10 ///////////////////////////SubMain//////////////////////////////////
    11 int main(int argc, char *argv[])
    12 {
    13 #ifndef ONLINE_JUDGE
    14     freopen("in.txt", "r", stdin);
    15     freopen("out.txt", "w", stdout);
    16 #endif
    17     int n, m;
    18     while(cin >> n >> m && n > 0)
    19     {
    20         memset(dp, -1, sizeof(dp));
    21         dp[0] = 0;
    22         for (int i = 0; i < n; ++i)
    23         {
    24             cin >> A[i];
    25         }
    26         for (int i = 0; i < n; ++i)
    27         {
    28             cin >> C[i];
    29         }
    30         for (int i = 0; i < n; ++i)
    31         {
    32             for (int j = 0; j <= m; ++j)
    33             {
    34                 if (dp[j] >= 0)
    35                 {
    36                     dp[j] = C[i];
    37                 }
    38                 else if (j < A[i]                        // 用一个就超出,不能用
    39                         || dp[j - A[i]] <= 0)       // 连凑比j小的数的时候都用完了,此时更加用完了
    40                 {
    41                     dp[j] = -1;
    42                 }
    43                 else
    44                 {
    45                     dp[j] = dp[j - A[i]] - 1;     // 用上了一个第i个硬币
    46                 }
    47             }
    48         }
    49         int answer = count_if(dp + 1, dp + 1 + m , bind2nd(greater_equal<int>(), 0)); // 总额0不算在答案内
    50         cout << answer << endl;
    51     }
    52 #ifndef ONLINE_JUDGE
    53     fclose(stdin);
    54     fclose(stdout);
    55     system("out.txt");
    56 #endif
    57     return 0;
    58 }
    59 ///////////////////////////End Sub//////////////////////////////////
    View Code

    提交,AC,并且足足算了2秒钟。这就是男人八题么

     
  • 相关阅读:
    HDU 5977 Garden of Eden(点分治求点对路径颜色数为K)
    HDU 5828 Rikka with Sequence(线段树区间加开根求和)
    TZOJ 1689 Building A New Barn(求平面上有几个其它点求到n个点的曼哈顿距离最小)
    HDU 5734 Acperience(数学推导)
    POJ 1741 Tree(点分治点对<=k)
    HDU 5723 Abandoned country(kruskal+dp树上任意两点距离和)
    HDU 5988 Coding Contest(最小费用最大流变形)
    TZOJ 1693 Silver Cow Party(最短路+思维)
    TZOJ 4602 高桥和低桥(二分或树状数组+二分)
    TZOJ 2099 Sightseeing tour(网络流判混合图欧拉回路)
  • 原文地址:https://www.cnblogs.com/UniqueColor/p/4776798.html
Copyright © 2011-2022 走看看