多重背包的二进制拆分法
在多重背包的直接拆分法中,个数为$c[i]$的物体被拆成$c[i]$种不同的物体
这样就使得物体的种类增加了很多,使得算法效率很低。
上述方法把$c[i]$拆成$c[i]$个1,于是任意选择可以表示出$1$到$c[i]$之间的所有数,从而达到多重背包的目的
想到,从$2^0,2^1,2^2,...,2^k$任意选择可以表示出$1$到$2^{k+1}-1$之间的所有数
于是就有了二进制拆分法 :
首先找到最大的$k$,使得$\sum_{i=0}^{k}2^i<=c[i]$
令$t=\sum_{i=0}^{k}2^i$,易知,上面$k+1$个数可以组成$1$到$t$之间的任何数
那$t+1$到$c[i]$之间的数怎么表示呢?
令$p=c[i]-t$,再拆出一个p,就可以了,理由如下:
$c[i]=p+t,c[i]-1=p+t-1...$依次类推
由于$1$到$t$已经被表示,所以这样一来$1$到$c[i]$之间的每个整数都可以被表示
具体来说,就是把数量为$c[i]$的物体分为$k+2$个,它们的体积分别为$2^0*v[i],2^1*v[i],...,2^k*v[i],p$
Code:
是$POJ1742Coins$的代码,虽然会TLE,就当打了个二进制拆分法的板子叭
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #define Ri register int 5 #define il inline 6 #define mem(a,b) memset(a,b,sizeof(a)) 7 #define go(i,a,b) for(Ri i=a;i<=b;i++) 8 #define yes(i,a,b) for(Ri i=a;i>=b;i--) 9 using namespace std; 10 const int N=101,M=100001; 11 int n,m,t,p,ans,a[N],c[N]; 12 bool f[M]; 13 il void calc(int x) 14 { 15 t=1,p=0; 16 while(t<=x){t+=(t<<1);p++;} 17 t=x-t/3;p--; 18 } 19 int main() 20 { 21 while(scanf("%d%d",&n,&m)&&n) 22 { 23 mem(f,0);ans=0; 24 go(i,1,n)scanf("%d",&a[i]); 25 go(i,1,n)scanf("%d",&c[i]); 26 f[0]=1; 27 go(i,1,n) 28 { 29 calc(c[i]); 30 go(j,0,p) 31 { 32 int q=1;if(j)q*=2; 33 yes(k,m,a[i]*q) 34 f[k]|=f[k-a[i]*q]; 35 } 36 yes(k,m,t)f[k]|=f[k-t]; 37 } 38 go(i,1,m)if(f[i])ans++; 39 printf("%d\n",ans); 40 } 41 return 0; 42 }
光伴随的阴影