HDU 2844+POJ 1014 +FZU 1432详解(多重背包&&二进制优化)

相信很多人都在查资料的过程当中,了解到了二进制优化这个概念,但是还是感觉云里雾里的。既然无法知其所以然,那就退而求其次,知其然好了。

具体方法:

如果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;
}





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值