区间圆数 / Round Numbers S
题目链接:ybt高效进阶5-3-2 / luogu P6218
题目大意
问你 l~r 中,有多少个数满足它的二进制表示中 0 的个数不小于 1 的个数。
思路
看到这些二进制,不难想到用数位 DP。
首先,我们设
f
i
,
j
f_{i,j}
fi,j 为二进制中
i
i
i 位数有
j
j
j 个是
0
0
0 的有多少个数。
不难得出转移
f
i
,
j
=
f
i
−
1
,
j
+
f
i
−
1
,
j
−
1
f_{i,j}=f_{i-1,j}+f_{i-1,j-1}
fi,j=fi−1,j+fi−1,j−1。
然后考虑对于
k
k
k 位数,我们要满足
0
0
0 的个数比
1
1
1 的个数至少多
x
x
x 要怎么求。
不难想到从大到小枚举
x
x
x 为
k
k
k 位数有多少个
0
0
0,在仍满足条件的时候就把
f
f
f 值加上。
把它弄成一个函数,叫做
s
u
m
(
k
,
x
)
sum(k,x)
sum(k,x)
那不难想到要怎么做了。
那有两种情况:
第一种:
[
2
i
,
2
i
+
1
−
1
]
[2^i,2^{i+1}-1]
[2i,2i+1−1] 那这种就是
s
u
m
(
i
−
1
,
1
)
sum(i-1,1)
sum(i−1,1)
因为最高位是
1
1
1 已经确定,后面的都是随便填。
第二种:你高位都是压到最大的,然后后面有一个可以
1
1
1 的选了
0
0
0,那你后面的都可以任意选了。
那你就从高位往低位跑,先通过一个数组维护前面
1
1
1 比
0
0
0 多多少个。
然后如果这一位是
1
1
1,那就可以这么搞,然后就丢进去搞。
(记得这一位
x
x
x 位是一定要填
0
0
0,所以答案是
f
x
−
1
,
n
u
m
−
1
f_{x-1,num-1}
fx−1,num−1)
(
n
u
m
num
num 是你前面搞出来
1
1
1 比
0
0
0 多的个数)
代码
#include<cstdio>
using namespace std;
int l, r, f[41][41], bef, a[41];
void DP() {
f[0][0] = 1;
for (int i = 1; i <= 32; i++)
for (int j = 0; j <= i; j++) {
if (j == 0) f[i][j] = f[i - 1][j];
else f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
}
}
int get_sum(int sz, int mor) {//求二进制k位有多少个数0个数比1个数知道多mor
int re = 0;
for (int i = sz; i >= 0; i--)//枚举这个二进制数有多少个0
if (i + i < sz + mor) break;
else re += f[sz][i];
return re;
}
int work(int x) {
int ans = 0;
int tmp = x, sz = 0;
while (tmp) {
sz++;
a[sz] = (tmp & 1);
tmp >>= 1;
}
for (int i = 1; i < sz; i++)//可以任意填1
ans += get_sum(i - 1, 1);
int num = 1;
for (int i = sz - 1; i >= 1; i--)
if (a[i]) {//前面的压着,这个放开,填后面才可以任意填
ans += get_sum(i - 1, num - 1);
num++;
}
else num--;
if (num <= 0) ans++;//最后判断 x 是否合法
return ans;
}
int main() {
DP();
scanf("%d %d", &l, &r);
printf("%d\n", work(r) - work(l - 1));
return 0;
}