题目传送门
题目描述:
给定一个区间[l,r],问l到r的整数中有几个转换成二进制数后0比1多(不计前导零)。
Solution
- 首先,我们可以引出一个性质:
对于任何一个二进制数n,将它各个位数之间的一变成0所组成的若干个区间恰好包含 [ 0 , n ] [0,n] [0,n]这个区间
例如对于整数11,它二进制拆分后是
1011
1011
1011
按照上述过程进行拆分能得到多个不同的二进制数:
1010
(
10
)
1010(10)
1010(10) ,
1000
(
8
)
1000(8)
1000(8) ,
0000
(
0
)
0000(0)
0000(0)
它们互相组合,可以得到整个区间:
[
0000
,
1000
)
[0000,1000)
[0000,1000),
[
1000
,
1010
)
[1000,1010)
[1000,1010)
以及1011
可以发现这样就可以把
1
−
n
1-n
1−n之间的数全都包含,这样就可以用类似前缀和的形式求出答案:
w
o
r
k
(
r
+
1
)
−
w
o
r
k
(
l
)
work(r+1)-work(l)
work(r+1)−work(l)
其中
r
+
1
r+1
r+1,
l
l
l是因为work求的是比work()中的数小的数,是因为我们没有把最后那个落单的整数算上去,只算到了
[
1
,
n
−
1
]
[1,n-1]
[1,n−1]区间。
我们将二进制处理出来之后,我们就可以枚举每一位,如果当前位是0,那么就把0的总数加1,否则就将答案累加。
我们预处理出组合数 C i j C_i^j Cij表示在i个可以选的位置中插入j个0的合法方案数
-
如何累加呢?
想要方案是合法的,0就必须大于等于 n + 1 2 \frac {n+1}2 2n+1,这个是很想然的当查询到一个1的时候,我们已知当前0的个数为 z n zn zn个,当前位置为i
想要使方案合法,就至少添加 n + 1 2 − z n − 1 \frac {n+1}2 - zn-1 2n+1−zn−1个0(减1是因为我们把当前1搞成了0),而上限则是i,说明全部都为0,添加上组合数即可
设二进制长度为len
而上述过程计算的是长度等于len的方案数。
由于这里是计算
[
1
,
n
−
1
]
[1,n-1]
[1,n−1]之间所有的方案数,所以我们还要计算长度小于len的方案数。
双重循环累加即可,过程与上述相似。
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<string>
#include<vector>
#include<stack>
#include<set>
#include<map>
#include<queue>
using namespace std;
int L,R,len = 0;
int c[6060][6060];
int a[1010100];
void init(){
for (int i=0;i<50;i++)
for (int j=0;j<=i;j++)
c[i][j] = i==j||j==0?1:c[i-1][j-1]+c[i-1][j];
}//预处理组合数
void cl(int x){
while (x){
a[++a[0]] = x&1?1:0;
x>>=1;
cnt++;
}
}//二进制拆分处理
int work(int x){
a[0] = 0;
int ans = 0;
len = 0;
cl(x);
for (int i=1;i<a[0]-1;i++)
for (int j=i/2+1;j<=i;j++)
ans += c[i][j];//长度小于len(a[0])
int zn = 0;//0的个数
for (int i = a[0]-1;i>=1;i--)
if (a[i])
for (int j=(a[0]+1)/2-zn-1;j<i;j++)
ans += c[i-1][j];//同上述过程,改成0的方案数
else zn++;//0的个数++
return ans;
}
signed main(){
init();
while (~scanf("%d %d",&L,&R)){
int sum = work(R+1) - work(L);
printf("%d\n",sum);
}
}
//ps:tm的不知道为什么,这题开long long会炸,大家千万不能开long long!!!