题意:有权值分别为1,2,3,4,5,6的大理石,每种都有若干块,能否把它们分成权值相等的2份。大理石的总数量不超过20000。(多重背包)
分析:判断dp[ V/2 ] ==V/2 即可,但过程如果用普通做法会超时,即多重背包当成01背包做效率很低,这时候要用二进制拆分优化,将复杂度变为
二进制拆分原理:
这里是指一个大数11101111 ,只要每一位上的1我们都有一个数,就可以表示出来这个大数 也就是用1 2 4 8 (1 10 100 1000..) 可以表示出任意的数 ,那么任意一个数都可以经过处理变为2进制的数,为了方便每个二进制的数只有一个,这个处理可以看作把13 分为 1、2、4、6 (最后一个6是为了处理方便避免重复) 。
二进制拆分:
for(int i=1;i<=n;i++) { for(int j=1;j<=num[i];j<<=1) //二进制每一位枚举. //注意要从小到大拆分 { num[i]-=j;//减去拆分出来的 new_c[++tot]=j*c[i];//合成一个大的物品的体积 new_w[tot]=j*w[i];//合成一个大的物品的价值 } if(num[i])//判断是否会有余下的部分. //就好像我们某一件物品为13,显然拆成二进制为1,2,4. //我们余出来的部分为6,所以需要再来一份. { new_c[++tot]=num[i]*c[i]; new_w[tot]=num[i]*w[i]; num[i]=0; } }
Code:
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define INF 0x3f3f3f3f using namespace std; const int maxn=120012; int n,V; int dp[maxn]; int a[7]; int v[maxn],w[maxn]; int main(){ int kase=1; while(scanf("%d %d %d %d %d %d",&a[1],&a[2],&a[3],&a[4],&a[5],&a[6])!=EOF){ if(a[1]==0&&a[2]==0&&a[3]==0&&a[4]==0&&a[5]==0&&a[6]==0) break; memset(dp,0,sizeof(dp)); printf("Collection #%d:\n",kase++); V=n=0; for(int i=1;i<=6;i++){ V+=a[i]*i; } if(V&1){ printf("Can't be divided.\n\n"); continue; } else{ int tot=0; for(int i=1;i<=6;i++){ for(int j=1;j<=a[i];j<<=1){ a[i]-=j; // printf("a[i]=%d j=%d\n",a[i],j); w[tot]=j*i; v[tot++]=j*i; } if(a[i]!=0){ v[tot]=a[i]*i; w[tot++]=a[i]*i; } } // printf("tot=%d\n",tot); for(int i=0;i<tot;i++){ for(int j=V/2;j>=w[i];j--){ dp[j]=max(dp[j],dp[j-w[i]]+v[i]); } } // for(int i=6;i>=1;i--){ // for(int k=v/2;k>=i;k--){ // for(int j=1;j<=a[i];j++){ // if(k-i*j>=0) // dp[k]=max(dp[k],dp[k-i*j]+i*j); // //// printf("dp[k]=%d\n",dp[k]); // } // } // } if(dp[V/2]==V/2) printf("Can be divided.\n\n"); else printf("Can't be divided.\n\n"); } } return 0; }