相信很多人都在查资料的过程当中,了解到了二进制优化这个概念,但是还是感觉云里雾里的。既然无法知其所以然,那就退而求其次,知其然好了。
具体方法:
如果1个物品有13件,其价值为3,重量为2.
首先我们把13件物品分解为二进制数1,10,100,对应的10进制分别为1,2,4.剩下一个6.
将其价值和重量分别乘以这个系数,我们将得到3,6,12,18和2,4,8,12.
我们可以理解为1种有13件的物品变成了4种物品,其分别只有1件。这样多重背包问题转化成了0-1背包问题.
首先我们来看,HDU 2844
题目大意:你有n种硬币,让你把这些硬币换成[1,m],有哪些可以做到.
比如
2 5
1 4 2 1
这两种硬币可以换成 1,2,4,5,6 但是6大于5,所以不符合。因此有4种符合。
思路:首先我们将其转化成背包问题.
假如5是背包容量,那么我们通过背包问题求得的能取得的最大值如果等于5,自然可以做到。
同理,假如4是背包容量,看结果是否等于4.
于是我们可以用一层循环,枚举背包容量1-m
当然此题,数据量较多,需要用到上述的二进制优化.
相信了解0-1背包的容易理解下面代码
// 多重背包 二进制优化
#include <iostream>
#include <algorithm>
#include <queue>
#include <math.h>
#include <stdio.h>
#include <string.h>
using namespace std;
int M[100010];
int main()
{
int A[110],C[110];
int n,m;
int i,j,k;
while(scanf("%d %d",&n,&m),n|m){
for(i=1;i<=n;i++)
scanf("%d",&A[i]);
for(i=1;i<=n;i++)
scanf("%d",&C[i]);
memset(M,0,sizeof(M));
int temp;
for(i=1;i<=n;i++)
{
k=1;
while(k<=C[i])
{
C[i]-=k;
temp=k*A[i];
for(j=m;j>=temp;j--)
M[j]=max(M[j],M[j-temp]+temp);
k=k<<1;
}
if(C[i]>0)
{
k=C[i];
temp=k*A[i];
for(j=m;j>=temp;j--)
M[j]=max(M[j],M[j-temp]+temp);
}
}
k=0;
for(i=1;i<=m;i++)
if(M[i]==i)
k++;
printf("%d\n",k);
}
return 0;
}
再来说说Poj 1014
题目大意:给你一堆物品,每堆的价值分别为1-6,数量已知。求是否能够均分这堆物品的价值
思路:
同样转化成背包问题,将总价值除以2,此时的价值可以视作背包容量。然后通过背包问题求出的最优解与容量比较,如果相等,便可以均分.
当然,同样也需要用到二进制优化,代码几乎和上述一模一样。所以,知道了这个方法,就可以解一类题了,
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int dp[220000];
int a[8];
int main()
{
int T=0;
while(cin>>a[1]>>a[2]>>a[3]>>a[4]>>a[5]>>a[6])
{
if(!a[1]&&!a[2]&&!a[3]&&!a[4]&&!a[5]&&!a[6])
break;
T++;
int sum=a[1]+2*a[2]+3*a[3]+4*a[4]+5*a[5]+6*a[6];
if(sum&1)
{
cout<<"Collection #"<<T<<":"<<endl;
cout<<"Can't be divided."<<endl;
cout<<endl;
continue;
}
sum=sum/2;
memset(dp,0,sizeof(dp));
int flag=0;
for(int i=1;i<=6;i++)
{
int k=1;
while(k<=a[i])
{
int t=k*i;
a[i]-=k;
for(int j=sum;j>=t;j--)
dp[j]=max(dp[j],dp[j-t]+t);
k=k<<1;
}
if(a[i]>0)
{
k=a[i];
int t=k*i;
for(int j=sum;j>=t;j--)
dp[j]=max(dp[j],dp[j-t]+t);
}
//cout<<dp[sum]<<endl;
if(dp[sum]==sum)
{
flag=1;
break;
}
}
if(flag)
{
cout<<"Collection #"<<T<<":"<<endl;
cout<<"Can be divided."<<endl;
cout<<endl;
}
else
{
cout<<"Collection #"<<T<<":"<<endl;
cout<<"Can't be divided."<<endl;
cout<<endl;
}
}
return 0;
}
再来看看FZU 1432
题目大意:给你几种硬币,问用这些硬币换成一定金额,求最少用多少硬币能够换成.
例如:
1 3
2 3
5 3
这三种面额的硬币,要换成18.
则最优解是,5*3+2*1+1*1 需要5枚硬币
思路:
同样可以将其转化成背包问题.但是要求的并不是最大价值,而是达到背包容量,但是却使用的件数最少。
所以递推式应该变化.相应的dp初始值也需要变化。
递推式:
dp[j]=min(dp[j],dp[j-t]+k);
由于是求最小值,故max改为min.
至于为什么是加k(二进制优化后的数)
可以这样理解,比如一种物品,有13件,将其分解成二进制数,1,2,4,6.
同样,将它的重量和价值分别乘以这个系数后,等于变成了4种物品。
比如选了第一种物品,就等于取了1件。
选了第三种物品,就等于取4件。
这里也是完全背包问题,由于数据量较大,故也需要进行二进制优化。
具体见代码.
#include <iostream>
#include <algorithm>
#include <queue>
#include <math.h>
#include <stdio.h>
#include <string.h>
using namespace std;
int dp[110056];
int Cost[12];
int Num[12];
int n,m;
const int MAX=9999999;
int main()
{
while(scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;i++)
scanf("%d%d",&Cost[i],&Num[i]);
scanf("%d",&m);
for(int i=0;i<=m;i++)
dp[i]=MAX;
dp[0]=0;
for(int i=1;i<=n;i++)
{
int k=1;
while(k<=Num[i])
{
int t=k*Cost[i];
Num[i]-=k;
for(int j=m;j>=t;j--)
dp[j]=min(dp[j],dp[j-t]+k);
k=k<<1;
}
if(Num[i]>0)
{
int t=Num[i]*Cost[i];
for(int j=m;j>=t;j--)
dp[j]=min(dp[j],dp[j-t]+k);
}
}
if(dp[m]!=MAX)
printf("%d\n",dp[m]);
else
printf("-1\n");
}
return 0;
}