http://poj.org/problem?id=1014
题意:
有6个价值为 1 2 3 4 5 6的物品,每个背包分别有Ai个
求是否能选够 总价值为n的 物品集合
max_Ai=20000
所以max_n= 20000*6=12W;
我们把多重背包问题转化为01背包就方便很多了,但是显然不能看成 Ai个价值为j的物品
这样的话 背包数最大是2W,j最大是12W,2W*12W超时了。
用到一个二进制优化
我们在dp的过程中,【需要把 选上1到Ai个物品的情况都要考虑到】,而我们 可以把 A【i】个价值一样的物品拆成log【Ai】+1堆物品,并把他们看成一个独立的物品
如13个物品K 拆成 , 1 2 4 个(都是2^k),余下 6 (13-2^k,这个k是最大的且满足13-2^k>=0的数)
那么显然 由 这四个新的独立 物品堆,每个含1 2 4 6单位的K物品, 【他们可以通过自由组合,得到j=1到13之间 任一个 j, 所有同时选择j个物品的情况都能被得到(1<=j<=13)】
至此,多重背包问题就转化为为了 一共有 ∑A[i]个物品,最大体积为sum的0-1背包问题,复杂度为 O( ∑A[i] *sum )
本题里面约为 log(2W)* 12W 能过
#include <cstdio> #include <cmath> #include <cstring> #include <string> #include <algorithm> #include <iostream> #include<map> using namespace std; int max(int a ,int b) { return a>b?a:b; } int tm[10]; int sum; int ok; int dp[120000]; struct node { int v; int num; }; node divide[15*6]; int main() { int i,j; int cnt=1; while(scanf("%d%d%d%d%d%d",&tm[1],&tm[2],&tm[3],&tm[4],&tm[5],&tm[6])) { sum=0; ok=0; for (i=1;i<=6;i++) { /* if (tm[i]>60) //60为1~6最小公倍数,任何一个tm[i]超过60,必然超出60*tm[i]的部分能被平均分配,所以只需要看60,61能否分配完成即可 { if (tm[i]%2) tm[i]=61; else tm[i]=60; } */ sum+=tm[i]*i; } if (!sum) break; if (cnt!=1) printf("\n"); printf("Collection #%d:\n",cnt++); if (sum%2) { printf("Can't be divided.\n");continue;} int cun=1; for (i=1;i<=6;i++) //二进制拆分 { int k=1; int m=tm[i]; while(k<=m) { divide[cun].v=i; divide[cun++].num=k; m=m-k; k=k*2; } if (!m) continue; //价值为0的物品集无意义 divide[cun].v=i; divide[cun++].num=m;//余数 } sum=sum/2; memset(dp,0,sizeof(dp)); dp[0]=1; for (i=1;i<cun;i++) { int tmp=divide[i].num*divide[i].v;//取得该物品的价值 ,物品的体积都看作1 if (!tmp) continue; for (j=sum;j>=tmp;j--) dp[j]=max(dp[j],dp[j-tmp]); //是否能选择到价值为j的物品,分为前i-1个物品恰好得到价值j的背包,或者前i-1个物品得到价值为j-tmp的背包; } if (dp[sum] ) ok=1; if (ok) printf("Can be divided.\n"); else printf("Can't be divided.\n"); } return 0; }