背包问题更深化的理解-多重背包的二进制拆分

这篇文章主要证明一下多重背包的二进制拆分的可行性与正确性:

类似于二进制的原理:一定可以表达一系列连续的正数,下面用例子证明

       把22进行二进制拆分:

         成为1,2,4,8,7;由1,2,4,8可以组成1--15之间所有的数,而对于16--22之间的数,可以先减去剩余的7,那就是1--15之间的数可以用1,2,4,8表示了。

例题:

NOI 8756:砝码称重V2

总时间限制: 
1000ms
 
内存限制: 
65536kB
描述

设有1g、2g、3g、5g、10g、20g的砝码各若干枚(其总重<=100,000),要求:计算用这些砝码能称出的不同重量的个数,但不包括一个砝码也不用的情况。

输入
一行,包括六个正整数a1,a2,a3,a4,a5,a6,表示1g砝码有a1个,2g砝码有a2个,……,20g砝码有a6个。相邻两个整数之间用单个空格隔开。
输出
以“Total=N”的形式输出,其中N为可以称出的不同重量的个数。
样例输入
1 1 0 0 0 0
样例输出
Total=3
提示
样例给出的砝码可以称出1g,2g,3g三种不同的重量。
注意:对于拆分剩余是0,因题目的意思要特别处理。
#include<iostream>
using namespace std;
#include<cstdio>
int a[]={0,1,2,3,5,10,20};
#define MAX 100100
bool f[MAX];
int val[MAX];
int t=0;
int sum=0;
void input()
{
    int count=0,b=0;
    for(int i=1;i<=6;++i)
    {
        scanf("%d",&b);
        sum+=b*a[i];
        count=a[i];
        if(b>1)
        {
            for(int i=1;i<=b;i<<=1)
            {
                ++t;
                val[t]=count*i;
                b-=i;
            }
            if(b>0)
            {
                ++t;
                val[t]=count*b;
            }
            continue;
        }
         
        if(b==1)
        {
            ++t;
            val[t]=count;
        }
    }
}
void DP()
{
    f[0]=true;
    for(int i=1;i<=t;++i)
      for(int j=sum;j>=val[i];--j)
      f[j]=f[j]||f[j-val[i]];/*注意val才是拆成的背包*/
    int p=0;
    for(int i=1;i<=sum;++i)
    if(f[i])
    p++;
    printf("Total=%d\n",p);
}
int main()
{
    input();
    DP();
    return 0;
}
View Code

 

  

posted @ 2016-04-07 10:59  csgc0131123  阅读(1837)  评论(0编辑  收藏  举报