CSDN編程挑戰 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]; //CSDN編譯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.csdn.net/fyfmfof/article/details/29626295