Coins HDU2844 多重背包

题意:一些不同价值和一定数量的硬币,求用这些硬币可以组合成价值在[1 , m]之间的有多少

分析:初始 d[] 为负无穷,然后多重背包,最后统计d[]中有多少是大于0的。

递归方程只需将完全背包题目的方程略微修改,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。有状态转移方程:

f[i][v] = max{ f[i-1][v-k*c[i]] + k*w[i] | 0 <= k <= n[i] }   

  复杂度是 O(V*Σn[i])。

当且仅当f[i]的状态只与f[i-1]有关时,可以将二维数组简化为一维。

 

解法一:

网友思路:多重背包。但不能直接转化为01背包求,因为数据太多。但是可以增加一个一维数组use[i]记录 到达i元时j种钱用的次数

(dp[j - val[i]] && !dp[j] && use[j - val[i]] < num[i])

表示的是能由上一步得到,且当前硬币个数从未实现过,达到上一步时该种类硬币使用的最少个数+1不能超过该种类硬币总个数。每添加一种新的硬币,都要清零used数组,表示目前具备的任意状态都没有新硬币的参与。然后一种一种添加,直到超过该种硬币个数。

完整代码:

 1 #include <stdio.h>
 2 bool dp[100001];
 3 int use[100001];
 4 int n, m;
 5 int val[101], num[101];
 6 int main(){
 7     dp[0] = 1;
 8     while (~scanf("%d %d", &n, &m)){
 9         if (n == 0 && m == 0)break;
10         for (int i = 1; i <= n; ++i)scanf("%d", &val[i]);
11         for (int i = 1; i <= n; ++i)scanf("%d", &num[i]);
12         for (int i = 1; i <= m; ++i)dp[i] = 0;
13         int ans = 0;
14 
15         for (int i = 1; i <= n; ++i){
16             for (int j = 1; j <= m; ++j)use[j] = 0;
17             for (int j = val[i]; j <= m; ++j){
18                 if (dp[j - val[i]] && !dp[j] && use[j - val[i]] < num[i]){
19                     dp[j] = 1;
20                     ++ans;
21                     use[j] = use[j - val[i]] + 1;
22                 }
23             }
24         }
25         printf("%d\n", ans);
26     }
27     return 0;
28 }


解法二:

定理:正整数n可以被分解成1, 2, 4,…, 2^(k-1), n-2^k+1 (k是满足n-2^k+1>0的最大整数)的形式,且1~n之内的所有整数均可唯一表示成1, 2, 4, …, 2^(k-1), n-2^k+1中某几个数的和的形式

1、2、4可以组合出所有小于8的数;

1、2、4、8可以组合出所有小于16的数;

1、2、4、8、16可以组合出所有小于32的数;

将第i种物品分成若干件物品,每件物品有一个系数,这件物品的费用和价值均是本来的费用和价值乘以这个系数。使这些系数分别为 1,2,4,...,2^(k-1),n[i] - 2^k + 1,且k是满足n[i] - 2^k + 1 > 0的最大整数。例如,n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示,这个证明可以分0..2^k-1和2^k..n[i]两段来分别讨论得出。

这样就将第i种物品分成了O(log n[i])种物品,将原题目转化为了错杂度为O(V*Σlog n[i])的01背包题目,是很大的改进。

完整代码:

#include <stdio.h>
bool dp[100001];
int n, m;
int val[101], num[101];
int main(){
    dp[0] = 1;
    while (~scanf("%d %d", &n, &m)){
        if (n == 0 && m == 0)break;
        for (int i = 1; i <= n; ++i)scanf("%d", &val[i]);
        for (int i = 1; i <= n; ++i)scanf("%d", &num[i]);
        for (int i = 1; i <= m; ++i)dp[i] = 0;
        int ans = 0;

        for (int i = 1; i <= n; ++i){
            int cnt = num[i];
            for (int k = 1; k <= cnt; k <<= 1){
                for (int j = m; j >= val[i] * k; --j){
                    dp[j] |= dp[j - val[i] * k];
                }
                cnt -= k;
            }

            if (!cnt)continue;
            for (int j = m; j >= val[i] * cnt; --j){
                dp[j] |= dp[j - val[i] * cnt];
            }
        }

        for (int i = 1; i <= m; ++i)ans += dp[i];
        printf("%d\n", ans);
    }
    return 0;
}

 

posted @ 2018-01-16 10:44  proscientist  阅读(371)  评论(0编辑  收藏  举报