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:\n",++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.\n\n"); 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\n\n",m==dp[m]?"Can be divided.":"Can't be divided."); } return 0; }