题目描述:
给定一个区间 [ l , r ] [l,r] [l,r],问 l l l到 r r r的整数中有几个转换成二进制数后 0 0 0比 1 1 1多(不计前导零).
分析:
要求
[
l
,
r
]
[l,r]
[l,r]中的整数中有几个转换成二进制数后
0
0
0比
1
1
1多,可以通过求
[
1
,
r
+
1
]
−
[
1
,
l
]
[1,r+1]-[1,l]
[1,r+1]−[1,l]来转换, 即小于
r
+
1
r+1
r+1的符合题意的数量
−
-
−小于
l
l
l的符合题意的数量 =
l
l
l到
r
r
r的符合题意的数量。
要求
[
1
,
k
]
[1, k]
[1,k]的答案,可以进行分类讨论。
设
k
k
k转化成二进制以后是
l
e
n
len
len位
-
1.
1.
1. 对于小于
l
e
n
len
len位的,除了最高位是
1
1
1,其余位数在满足题意的情况下可以乱填。
答案就是: ∑ i = 1 l e n − 1 ∑ j = i / 2 + 1 i C [ i ] [ j ] \sum_{i=1}^{len-1}\sum_{j=i/2+1}^{i} C[i][j] ∑i=1len−1∑j=i/2+1iC[i][j], C [ i ] [ j ] C[i][j] C[i][j]表示 C i j C_{i}^{j} Cij,可以用杨辉三角求出, w h y why why?自行百度(其实我也不知道) - 2. 2. 2. 对于等于 l e n len len位的,我们从高位往低位做,用 Z e r o Zero Zero记录 0 0 0的个数,遇到 0 0 0, Z e r o ​ + ​ 1 Zero\!+\!1 Zero+1;遇到 1 1 1,我们将这一位的 0 0 0改成 1 1 1,这样更改以后,在接下来的几位中,就可以随便填数,也都能保证形成的数比 k k k小。我们设在接下去要填的数字中要填 a a a个 1 1 1, b b b个 0 0 0, i i i为当前正在处理位的下标,因为题目有限制: 0 0 0比 1 1 1多,又因为现在已经有了 Z e r o Zero Zero个 0 0 0,因此 b b b满足 b ≥ ( l e n + 1 ) / 2 − ( Z e r o + 1 ) b\geq(len+1)/2-(Zero+1) b≥(len+1)/2−(Zero+1),因此就可以累加答案: ∑ j = ( l e n + 1 ) / 2 − ( Z e r o + 1 ) i − 1 C [ i ​ − ​ 1 ] [ j ] \sum_{j=(len+1)/2-(Zero+1)}^{i-1} C[i\!-\!1][j] ∑j=(len+1)/2−(Zero+1)i−1C[i−1][j].
- 这样子就求出了所有 [ 1 , k ] [1,k] [1,k]的符合题意的数字的数量。
代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1001, P = 50;
int C[N][N], f[N];
int n, m;
void Temp() {
for (int i=0;i<=P;i++) {
for (int j=0;j<=i;j++) {
if (j == 0 || i == j) {
C[i][j] = 1;
continue;
}
else C[i][j] = C[i-1][j-1] + C[i-1][j];
}
}
return ;
}
inline long long fun(int x) {
memset(f, 0, sizeof(f));
int len = 0, fm = x;
while (fm) {
f[++ len] = fm % 2;
fm /= 2;
}
long long ans = 0;
for (int i=1;i<len-1;i++) {
for (int j=i/2+1;j<=i;j++)
ans += (long long)C[i][j];
}
int Zero = 0;
for (int i=len-1;i>=1;i--) {
if (f[i] == 1) {
for (int j=(len+1)/2-Zero-1;j<=i-1;j++) {
ans += C[i-1][j];
}
}
if (f[i] == 0) ++ Zero;
}
return ans;
}
int main() {
scanf("%d%d",&n,&m);
Temp();
printf("%lld",fun(m+1) - fun(n));
return 0;
}