优快云编程挑战 2的补码
2的补码
题目详情:
在计算机中,整数是以2的补码的形式给出的。
给出整数A和B,假设计算机是32位机,求从A到B之间的所有二进制数中,一共用了多少个1。
输入格式:
多组数据,每组数据一行,由两个整数A,B,-2147483648<=A<=B<=2147483647
输出格式:
每组输出一行,从A到B使用的1的个数。
答题说明:
输入样例
-2 0
0 0
0 1
输出样例:
63
0
1
题目分析:
读完题目我们很自然的想到了位运算这个东西,当然博主也是直接上手就用位运算写下了这题,却没仔细想到题目那巨大的数据量会导致直接超时,虽然用上了快速位运算(n&=n-1)依然超时告终,冷静下来想想这道题目明显不能用遍历区间的每一个数字的二进制位去计算最终结果,因为差不多4*10^9数量的数据就算只是循环一边都差不多超时了,所以只能另辟蹊径了。想来想去貌似也只能通过区间头尾值来进行计算得到结果了,于是着手分析补码的一些情况。大致思路是想将区间首值到0之间的1的个数计算出来,再将尾值到0之间的1的个数计算出来,然后在根据首尾值的正负情况来计算最后结果。
解题步骤:
1.打印一张表:
先来考虑一下正数的情况(负数由于补码的原因不太好处理,所以会转成正数来处理,这个后面来说),我们通过一些二进制数可以看出下图的情况,并且可以用数学归纳法推导出下图中的公式:
然后根据这个规律我们可以得到0到n位二进制数最大值中所有数字补码中所含1的个数,然后在一开始打出一张表方便后面的计算。
2.计算首值或者尾值到0之间所有数字补码中所含1的个数
我们不妨用一个二进制数来试一下应该如何进行计算,拿1110来说吧,如果前两位不变,那么比1110要小的值则为1101和1100两个数;而当最高位不变时比1100还要小的数为1011、1010、1001、1000四个数;然后比1000还要小的4位2进制数则为0111、0110、0101、0100、0011、0010、0001、0000这8个数。
在这个例子中我们不难看出在分别计算这3层数字的1的个数时我们可以用到我们之前打印的表。第一层即前两位不变时,忽略掉不变的数字,变化的数字中1的个数就是我们之前打印的表中的a(1)的值,然后不变的部分中所有1的个数则是2的1次方乘上不变位中1的个数;第二层即最高位不变时的几个数字中所含1的个数总和等于a(2)加上不变位中1的个数乘上2的2次方;第三层中数字1的个数则就等于a(3)。
于是我们从这里可以推导出计算规则(正数):设当前所指位置为第n位(从低到高),0到该数字之间所有数字的补码中所含1的个数(不含自身1的个数)等于n从数字为1的最高一位的起,并依次移到下一个数字1的位数直至最低一位的1中a(n-1)+2^(n-1)*bit的值的总和(bit指位置比当前n位要高的1的个数)。
3. 关于负数
由于补码的编码中负值的补码等于符号位不变,其它位取反+1的结果,所以我们将符号位最后计算并将负数转换成正数进行计算即可,直接的方法就是让值等于INT_MAX+负数+1,这样就能得到负数补码中符号位相反,其它位相同的正值,然后用推导好的方法计算出0到该正数中1的个数,然后用最大正数到0中1的个数(即a(31))减去算出的个数就是该负数到0之间所有数补码中1的个数(这个结果忽略了所有的符号位并且包含了负数本身的1)。
4.最终结果计算
正数负数都处理完了,接下来的就是根据首尾值的正负情况等进行最终计算了,剩下的就在代码里说吧。
AC代码:
#include<iostream>
#include<cstdio>
#include<climits>
using namespace std;
int main()
{
const int INT_31=1<<30; //定义最高数值位的位相与常量,即第31位为1
int i,n,m,num,bit1,bit2;
__int64 count,start,end; //值比较大,用__int64存储
__int64 x[32];
x[0]=0;
x[1]=1; //初始1位0位的表值
for(i=2;i<32;i++) //打表
x[i]=(1<<(i-1))+2*x[i-1]; //优快云编译pow函数不知道为什么不让用所以这里用1<<(i-1)来表示2的i-1次方
while(scanf("%d%d",&m,&n)!=EOF)
{
start=end=count=0;
bit1=bit2=0;
/*计算首值到0之间1个数的值*/
if(m<0)
num=m+INT_MAX+1;
else
num=m;
if((num&INT_31)==INT_31)
{
count++; //用count计算首值中1的个数
start+=x[30]; //用start计算0~首值之间1的个数
bit1++; //bit用来计算比当前位高的1的个数
}
for(i=30;i>0;i--)
{
num<<=1;
if((num&INT_31)==INT_31)
{
count++;
start+=x[i-1]+bit1*(1<<(i-1));
bit1++;
}
}
if(m<0)
start=x[31]-start-bit1;
/*计算尾值到0之间1个数的值*/
if(n<0)
num=n+INT_MAX+1;
else
num=n;
if((num&INT_31)==INT_31)
{
count++; //用count继续累计尾值中1的个数
end+=x[30]; //基本同start
bit2++; //同上
}
for(i=30;i>0;i--)
{
num<<=1;
if((num&INT_31)==INT_31)
{
count++;
end+=x[i-1]+bit2*(1<<(i-1));
bit2++;
}
}
if(n<0)
end=x[31]-end-bit2; //负数则减掉自身1的数值以保证统一性
/*最结果的计算*/
if(m==n) //如果两数相等则直接将count除2即可
{
count/=2;
if(m<0) //若小于0则加上符号位
count++;
}
else if(m<0&&n<0) //两数小于0则将start值减去end加上符号位加上自身值并减掉重复计算的位置1的个数
count+=start-end+n-m+1-bit2;
else if(m>=0&&n>=0) //两数大于等于0则将end值减去start加上自身值并减掉重复计算的位置1的个数
count+=end-start-bit1;
else //一个小于0一个大于0则start加上end加上符号位加上自身值即可
count+=start+end-m;
printf("%I64d\n",count);
}
return 0;
}
解题总结:
总的来说还是略有难度的一道题目(至少对我来说→_→),花了几个小时,也不一定是最优的解法,但好歹也是做出来了,如果有更好解法欢迎交流~
本文固定链接:http://blog.youkuaiyun.com/fyfmfof/article/details/29626295