什么是二进制拆分?
一个数n拆成小于它的所有二的次方的和(指数递增的)加上剩下一个数。实际上就是将这个数的二进制的每一位选或者不选就可以组成一个范围在0~n的数
举个例子:13可以拆成 2^0 、 2^1 、 2^2、和 6(6是剩下的那个数),也就是拆分成 1 2 4 6,那么通过选择这4个数的就可以表示0~13的任意数。
如:
0:一个都不选。
1:选1;
2:选2;
3:选1和2;
4: 选4;
5:选1和4;
6:选2和4;
7:选1和2和4;
8:选2和6;
9:选1和2和6;
10: 选4和6;
11:选1和4和6;
12:选2和4和6;
13:全选;
以下给出个人的证明其中把n直接用来表示n[i]
证明:
①证0 ~ 2^k-1( 2^(k+1)- 1 <= n)可以被表示
由幂级数(等比数列)求和可得:
2^0 + 2^1 + 2^2 + …… +2^k = 2^(k+1)-1
2^0 + 2^1 + 2^2 + …… +2^k < 2^(k+1)
所以一个k+1位的二进制数至少可以拆分为 k个 二的次方(指数递增),那么不难得到k位二进制数已经可以被这些拆出来的数表示了。
所以现在0~2^(k+1) - 1( 2^(k+1)-1 <= n < 2^(k+2) - 1 )的十进制数已经可以被表示了
②下证 2^k ~ n可以被这些数表示
明显拆分得到的所有数就可以组成n,
那么组成n的那些数减去1(除了2^0不选,其他都选)就可以得到 n-1
组成n的那些数减去2(除了2^1不选,其他都选)就可以得到 n-2
组成n的那些数减去3(除了2^0 和 2^不选,其他的都选)就可以得到 n-3
……………………
……………………
……………………
对于n-x 有
2^(k+1) - 1 <= n-x < 2^(k+2) - 1
2^(k+1)-1 <= n < 2^(k+2) - 1
上式子联立得: 0<=x < 2^(k+1)也就是 0 <= x <= 2(k+1) -1;
通过①结论可得x可被这些拆分出来的数表示。
也就是先将所有数( 2^0 、 2^1 、 2^2 + …… +2^k 和拆分剩下的那个数)都选,那么n就可以被表示出来,那么n-x可以通过从所有数中把组成X的数移出来表示x的数就就可以得到n-x
所以命题得证
应用:二进制拆分可以用在完全背包上,用来降低时间复杂度,把1~n枚举改为对拆分的这些数进行枚举,时间复杂度从O(n)降低到o(logn)
贴代码
非函数调用
#include <iostream>
using namespace std;
const int N = 110;
int n,V;
int w[N];
int v[N];
int s[N];
int dp[N];
int max(int i,int j){
return i > j?i:j;
}
int main(){
cin >> n >> V;
for(int i = 1;i <= n;++i)
cin >> v[i] >> w[i] >> s[i];
for(int i = 1;i <= n;i++){
if(s[i]*v[i]>=V){//但物品个数足够装满整个背包时,使用完全背包更快
for(int j = v[i];j <= V;j++){
dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
}
}
else//物品个数不够,则用01背包 因为物品个数不够装满整个包 所以 s[i] < V/v[i]
for(int key = 1;s[i] > 0;s[i]-=key,key*=2){//选择key个 每次Key 个i物品进行01背包
if(s[i]>=key){
for(int j = V;j >= key*v[i];j--){
dp[j] = max(dp[j],dp[j-key*v[i]] + key*w[i]);
}
}else//二进制拆分剩下的数
for(int j =V;j >= s[i]*v[i];j--)
dp[j] = max(dp[j],dp[j-s[i]*v[i]] + s[i]*w[i]);
}
}
int result = 0;
for(int j = 0;j <= V;j++){
result < dp[j] ? result = dp[j]:result = result;
}
cout << result;
return 0;
}
调用函数
#include <iostream>
using namespace std;
const int N = 110;
int n,V;
int w[N];
int v[N];
int s[N];
int dp[N];
int max(int i,int j){
return i > j?i:j;
}
void zeroonepack(int i){
for(int key =1;s[i]>0;s[i]-=key,key<<=1){
if(s[i]>=key)
for(int j = V;j >= key*v[i];j--)
dp[j] = max(dp[j],dp[j - v[i]*key] + w[i] *key);
else
for(int j = V;j >= s[i]*v[i];j--)
dp[j] = max(dp[j],dp[j - v[i]*s[i]] + w[i] *s[i]);
}
}
void completpack(int i){
for(int j = v[i];j <= V;j++)
dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
}
int main(){
cin >> n >> V;
for(int i = 1;i <= n;++i)
cin >> v[i] >> w[i] >> s[i];
for(int i = 1;i <= n;i++){
if(s[i]*v[i]>=V){//物品个数不受限
completpack(i);
}
else{//s[i] <V/v[i]; 物品个数受限
zeroonepack(i);
}
}
int result = 0;
for(int j = 0;j <= V;j++){
result < dp[j] ? result = dp[j]:result = result;
}
cout << result;
return 0;
}