题目描述
如果一个数的二进制中只有1个0,那么我们称这样的数是“美丽数”,比如510=1012。 现在给你一个区间a,b,求区间内有多少个“美丽数”。
输入
第一行是一个整数K,表示样例的个数。 每个样例是两个整数a和b。
输出
每行输出一个样例的结果。
样例输入
3
1 2
2 5
1 1000000000000000000
样例输出
1
2
1712
提示
第一个例子中,只有2是美丽数;
第二个例子中,2和5是美丽数;
解析:
这个题目其实就是防止超时,不过也要注意类型,正常的方法是打表,但是我这里使用数学规律解答。
注意到以下情况:
1.对n位二进制数,其美丽数是确定数目的。
2.对于题目的10进制数来说,2进制数明显时间复杂度低。
然后我们注意,对于取值范围的左值和右值的计算是不一样的。
总体步骤如下:
1.计算左值的二进制位在当前位数下的美丽数数目。
2.计算右值的二进制位在当前位数下的美丽数数目。
3.计算两位数之间的每个位数的美丽数总和,这个我们聪明的高斯应该能在10s内算出来吧。
4.加起来就是结果
主函数
int main(){
int k,l1,l2,r1,r2;
//这里真的不要用long long int !!!!
__int64 a,b;
scanf("%d",&k);
while(k--){
scanf("%I64d %I64d",&a,&b);
//好久没用过c了,以前都是用go
countleft(a,&l1,&l2);
countright(b,&r1,&r2);
countall(l1,l2,r1,r2);
}
return 0;
}
计算左值
void countleft(__int64 n,int *cnt,int *flag){
int i=0;
(*cnt)=0,(*flag)=0;
while(n!=0){
if(n&1){
(*cnt)++;
i++;
}else{
i=0;
}
(*flag)++;
n=n>>1;
}
//如果全1或者只有1个0
if((*flag)-(*cnt)<2){
//如果0不在最后面,看0第几位就有多少种情况
if(i+1<(*flag)){
(*cnt)=(*cnt)-i+1;
}
//如果0在最后面或者全1,就只有1种或者0种
else (*cnt)=(*flag)-(*cnt);
}//多个0
else{
(*cnt)=(*flag)-i;
}
}
这里解释一下:左值的美丽数的大小应该是大于左值的,所以可以分为以下几种情况:
1.全是1,这种就是典型的2^n-1,肯定在当前二进制位数下没一个美丽数
2.只有一个1,这种就要再分情况了,0出现在最后面也就是2^n-2这种情况就只存在一种情况。不在最后面的情况:二进制下0所在第几位的多少种情况(什么?看不懂,自己拿笔推算一下,从当前左值开始0往后面移动这时候的值是大于左值且是美丽数所以有0所在位数个)。
3.多个0的情况,猛一看觉得这种情况会很难,但是我放了一个i,有没有想过我为什么放一个i在那里,i表示从左到右左值二进制下连续的1的数目。(其实也是压缩前缀问题,这个就等价于前面只有一个0的情况,因为1100和1101的美丽数是一眼多的)。
计算完左值二进制下的美丽数再来计算右值二进制下的美丽数
void countright(__int64 n,int *cnt,int *flag){
int i=0;
(*cnt)=0,(*flag)=0;
while(n!=0){
if(n&1){
(*cnt)++;
i++;
}else{
i=0;
}
(*flag)++;
n=n>>1;
}
//如果只有一个0
if((*flag)-(*cnt)==1){
(*cnt)=i;
}//像111...00类型
else if((*cnt)==i){
(*cnt)--;
}
//如果有多个0
else
{
(*cnt)=i-1;
}
}
解析:整个过程其实和左值差不多,只要注意当前二进制下美丽数都比右值下就行,完整步骤:
1.先看是不是只有一个0的情况,这里为什么不看0是不是在最后呢?看最后cnt的取值,是会等于i,比如101和110,101中的0只能在第二位,110中的0可以在第二位,第三位。正好就是i值(当然这里肯定不是正好,自己推导一下就知道了)。
2.这里第二种情况是11…10…0这种特殊的,即在一个特定位置下前面都是1后面都是0。为什么特殊呢?因为只要在特定位置前进一位就是一个美丽数,所以美丽数的数目就是有多少1(当然第一个1不能取代啦)。
3.有多个0,这个也是第二种情况的变种,比如11100和11001这两个数,第一个由第二种情况知道有两个美丽数,注意我们这里求的美丽数是小于右值的,所以后面那些其实很多余,又不是多到可以到下一个美丽数,(因为它这个位置很尴尬,欸多一点的数人家多一个美丽数,少一点的数人家就是纯粹求美丽数的,它就卡在这里了^^_),总体就是和第二章情况一样。
最后再汇合就行,当然这里我也吃了很多苦头
void countall(int l1,int l2,int r1,int r2){
int cnt;
if(l2==r2){
cnt=r1+l1-l2+1;
}else{
cnt=l1+r1;
if(l2<r2-1){
cnt+=(r2-2)*(r2-1)/2-(l2)*(l2-1)/2;
}
}
// for(l2++;l2<r2;l2++){
// cnt+=l2-1;
// }
printf("%d\n",cnt);
}
解析:这里真的很尴尬,因为l2,r2这两个是指左值右值有多少位二进制下(习惯这么说话了),如果等于就要额外处理,这里我们想象一把尺子(才不是我不想画图):刻度为n,则左值n位二进制数最多有n-1个美丽数,右值最少有0个美丽数,题目保证了左值会小于右值这就好办了,我们只要求差值就好了。
下面那个算式是我懒得想了,直接两个求和公式减掉就出来了(当然我不知道为什么,这样做得到的时间是下面那样直接for循环的时间的两倍)
全部代码:
#include<stdio.h>
void countleft(__int64 n,int *cnt,int *flag){
int i=0;
(*cnt)=0,(*flag)=0;
while(n!=0){
if(n&1){
(*cnt)++;
i++;
}else{
i=0;
}
(*flag)++;
n=n>>1;
}
//如果全1或者只有1个0
if((*flag)-(*cnt)<2){
//如果0不在最后面,看0第几位就有多少种情况
if(i+1<(*flag)){
(*cnt)=(*cnt)-i+1;
}
//如果0在最后面或者全1,就只有1种或者0种
else (*cnt)=(*flag)-(*cnt);
}//多个0
else{
(*cnt)=(*flag)-i;
}
}
void countright(__int64 n,int *cnt,int *flag){
int i=0;
(*cnt)=0,(*flag)=0;
while(n!=0){
if(n&1){
(*cnt)++;
i++;
}else{
i=0;
}
(*flag)++;
n=n>>1;
}
//如果只有一个0
if((*flag)-(*cnt)==1){
(*cnt)=i;
}//像111...00类型
else if((*cnt)==i){
(*cnt)--;
}
//如果有多个0
else
{
(*cnt)=i-1;
}
}
void countall(int l1,int l2,int r1,int r2){
int cnt;
if(l2==r2){
cnt=r1+l1-l2+1;
}else{
cnt=l1+r1;
if(l2<r2-1){
cnt+=(r2-2)*(r2-1)/2-(l2)*(l2-1)/2;
}
}
// for(l2++;l2<r2;l2++){
// cnt+=l2-1;
// }
printf("%d\n",cnt);
}
int main(){
int k,l1,l2,r1,r2;
__int64 a,b;
scanf("%d",&k);
while(k--){
scanf("%I64d %I64d",&a,&b);
countleft(a,&l1,&l2);
countright(b,&r1,&r2);
countall(l1,l2,r1,r2);
}
return 0;
}