背包问题更深化的理解-多重背包的二进制拆分
这篇文章主要证明一下多重背包的二进制拆分的可行性与正确性:
类似于二进制的原理:一定可以表达一系列连续的正数,下面用例子证明
把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; }