POJ 1742 多重背包问题

来源:互联网 发布:app软件著作权范本 编辑:程序博客网 时间:2024/04/28 10:26

在《背包九读》里,作者提到了最后的一种基本背包问题是多重背包问题

在所有背包问题中,问题都是一致的:有若干个物体,P(Wi, Vi) ,每个重量用wi表示,选择后能获得的价值用Vi表示。然后有一个总容量,在满足不超过总容量的情况下,使得选择的物体尽量价值最大。

01背包中,物体只有一个要么选,要么不选。

在 完全背包问题中,每个物体都有无数个,也可以选择无数个。

在 多重背包中,物体的个数是受限的,用P(Wi, Vi, Ai)表示。最大可以选择Ai个。


在《背包九读》中,作者给出一种思路是将多重背包问题,拆解成01背包,拆解的原则是将物体分解。

假如我们有13个物体,那么如何分解成一组整数,使得他们的组合可以表示出 1 - 13中的任意一个呢,作者给出的思路是利用二进制的分解方法,将一个整数分解出 1 2 4 ...等因子。

但是最后一个因子是有学问的,因为我们不能使得分解出来的所有数的和大于Ai,所以最后一个数是 Ai - (1+2+...2^(k-1)) = Ai - (2^k - 1) > 0

所以上面的解就是将之分解为 1 2 4 6 这样的基。 然后将 1倍的物体i理解成新物体,2倍的物体i理解成一个新的物体.....

复杂度降低到 O(N * M * log(A)).


不过这个算法看起来似乎仍然没有那么漂亮。没有aha的感觉吧

楼教主的思路貌似不怎么好理解吧,感觉网上广泛流行的是使用如下的手段来做转换:从完全背包转换成多重背包问题。

当在处理 第 i 中硬币的时候,我们记录 获得每一个金钱值 所需要的 当前硬币的数量,如果这个数量满足条件(小于等于给定的数目),我们才会选择这中硬币,否则不会。

用一个数组,我们将完全背包问题加以限制,就实现了一个多重背包。

代码如下:


#include <stdio.h>#include <string.h>#define COIN_NUM 105#define MONEY_SUM 100005int coin[COIN_NUM];int amount[COIN_NUM];int dp[MONEY_SUM];int used[MONEY_SUM];int nCoin, nSum;int GetNumOfPayments(){memset(dp, 0, sizeof(dp));dp[0] = 1;int i ,j;for(i = 0; i < nCoin; i++){memset(used, 0, sizeof(used));for( j = coin[i]; j <= nSum; j++){
            //因为这个问题仅仅是 求出是否即可,所以用bool就可以实现if(dp[j-coin[i]] && !dp[j] && used[j-coin[i]] + 1 <= amount[i]){dp[j] = 1;used[j] = used[j-coin[i]] + 1;}}}int nTotal = 0;for( i = 1; i <= nSum; i++){nTotal += dp[i];}return nTotal;}int main(){int i,j;while(scanf("%d%d", &nCoin, &nSum) != EOF){if(nCoin == 0 && nSum == 0){break;}for(i = 0; i <  nCoin; i++){scanf("%d", coin + i);}for( i = 0; i < nCoin; i++){scanf("%d", amount + i);}printf("%d\n",GetNumOfPayments());}return 0;}

代码的复杂度是 O(N*M)应该是一个比较不错的解法了~

总之来说今天收获还是很大的,弄清楚了几个基本类型的背包问题,AC了四个相关练习,为接下来找工作又多了一份自信。

准备明天挑战FB的 Online Puzzle,好运~