HDU 1059 Dividing(多重背包)
http://acm.hdu.edu.cn/showproblem.php?pid=1059
题意:
如今有价值为1,2,3,4,5,6的6种物品, 它们的数量为num[i]( 1<=i<=6 )个. 如今要问的是是否能把全部的的物品分成两份且这两份物品的价值总和同样 ?
分析:
首先我们求出全部物品的价值和sum_val, 假设sum_val是奇数, 那么明显不能分. 那么sum_val为偶时, 我们令m=sum_val/2. 我能仅仅要看看从全部物品里面取物品是否能使得: “全部取的物品总价值==m?”
因为每一个物品的数目是num[i]个, 所以本题是一个多重背包问题.
事实上多重背包问题能够转成01背包来解, 可是效率不高. 所以这里我们按<<背包九讲>>中提到的二进制压缩的思想来解决本题.
令dp[i][j]==x 表示选前i种物品且总价值<=j的前提下, 全部被选物品能达到的最大价值.
对于val[i]*num[i]>=m的物品来说, 我们直接用一次全然背包就可以.
即dp[i][j] = max( dp[i-1][j] , dp[i][j-val[i]]+val[i] )
对于val[i]*num[i]<m的第i类物品, 我们把它看成是以下的多个物品的组合体:
1个 2个 4个… 2^(k-1)个 以及 num[i] – 2^k+1 个
我们对由第i种物品划分成的上述每堆物品做一次01背包就可以.
(为什么上面的划分能得到正确解?
由于上面的物品覆盖了我们对num[i]个第i类物品的全部可能选择)
终于所求: 看dp[n][m]是否等于m就可以.
AC代码:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=20000+5; int m;//DP最大不能超过的价值 int dp[maxn*5]; int val[]={1,2,3,4,5,6};//每种物品的价值 int num[10]; //每种物品的数量 //一次01背包过程 void ZERO_ONE_PACK(int cost,int val) { for(int i=m;i>=cost;i--) dp[i] = max(dp[i], dp[i-cost]+val); } //一次全然背包过程 void COMPLETE_PACK(int cost, int val) { for(int i=cost;i<=m;i++) dp[i] = max(dp[i], dp[i-cost]+val); } //一次多重背包过程 void MULTIPLE_PACK(int cost,int val,int sum) { //全然背包 if(cost*sum>=m) { COMPLETE_PACK(cost,val); return ; } //log(sum)次01背包 int k=1; while(k<sum) { ZERO_ONE_PACK(cost*k,val*k); sum -=k; k=k*2; } ZERO_ONE_PACK(sum*cost,sum*val); } int main() { int kase=0; while(scanf("%d%d%d%d%d%d",&num[0],&num[1],&num[2],&num[3],&num[4],&num[5])==6) { if(num[0]+num[1]+num[2]+num[3]+num[4]+num[5]==0) break; printf("Collection #%d: ",++kase); int sum_val=0; for(int i=0;i<6;i++) sum_val+= val[i]*num[i]; if(sum_val%2) { printf("Can't be divided. "); continue; } m=sum_val/2;//物品价值和的一半 memset(dp,0,sizeof(dp)); for(int i=0;i<6;i++) { MULTIPLE_PACK(val[i],val[i],num[i]); } printf("%s ",m==dp[m]?"Can be divided.":"Can't be divided."); } return 0; }