XTU1307 Beautiful Number

题目描述
如果一个数的二进制中只有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;
}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值