位運算簡介及實用技巧(二) 高階篇 1

2021-06-29 16:50:55 字數 4848 閱讀 6076

二進位制中的1有奇數個還是偶數個

我們可以用下面的**來計算乙個32位整數的二進位制中1的個數的奇偶性,當輸入資料的二進位制表示裡有偶數個數字1時程式輸出0,有奇數個則輸出1。例如,1314520的二進位制101000000111011011000中有9個1,則x=1314520時程式輸出1。

vari,x,c:longint;

begin

readln(x);

c:=0;

for i:=1 to 32 do

begin

c:=c + x and 1;

x:=x shr 1;

end;

writeln( c and 1 );

end.

但這樣的效率並不高,位運算的神奇之處還沒有體現出來。

同樣是判斷二進位制中1的個數的奇偶性,下面這段**就強了。你能看出這個**的原理嗎?

varx:longint;

begin

readln(x);

x:=x xor (x shr 1);

x:=x xor (x shr 2);

x:=x xor (x shr 4);

x:=x xor (x shr 8);

x:=x xor (x shr 16);

writeln(x and 1);

end.

為了說明上面這段**的原理,我們還是拿1314520出來說事。1314520的二進位制為101000000111011011000,第一次異或操作的結果如下:

00000000000101000000111011011000

xor  0000000000010100000011101101100

---------------------------------------

00000000000111100000100110110100

得到的結果是乙個新的二進位制數,其中右起第i位上的數表示原數中第i和i+1位上有奇數個1還是偶數個1。比如,最右邊那個0表示原數末兩位有偶數個1,右起第3位上的1就表示原數的這個位置和前乙個位置中有奇數個1。對這個數進行第二次異或的結果如下:

00000000000111100000100110110100

xor   000000000001111000001001101101

---------------------------------------

00000000000110011000101111011001

結果裡的每個1表示原數的該位置及其前面三個位置中共有奇數個1,每個0就表示原數對應的四個位置上共偶數個1。一直做到第五次異或結束後,得到的二進位制數的最末位就表示整個32位數裡有多少個1,這就是我們最終想要的答案。

計算二進位制中的1的個數

同樣假設x是乙個32位整數。經過下面五次賦值後,x的值就是原數的二進位制表示中數字1的個數。比如,初始時x為1314520(網友抓狂:能不能換乙個數啊),那麼最後x就變成了9,它表示1314520的二進位制中有9個1。

x := (x and $55555555) + ((x shr 1) and $55555555);

x := (x and $33333333) + ((x shr 2) and $33333333);

x := (x and $0f0f0f0f) + ((x shr 4) and $0f0f0f0f);

x := (x and $00ff00ff) + ((x shr 8) and $00ff00ff);

x := (x and $0000ffff) + ((x shr 16) and $0000ffff);

為了便於解說,我們下面僅說明這個程式是如何對乙個8位整數進行處理的。我們拿數字211(我們班某mm的生日)來開刀。211的二進位制為11010011。

+---+---+---+---+---+---+---+---+

| 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |   <---原數

+---+---+---+---+---+---+---+---+

|  1 0  |  0 1  |  0 0  |  1 0  |   <---第一次運算後

+-------+-------+-------+-------+

|    0 0 1 1    |    0 0 1 0    |   <---第二次運算後

+---------------+---------------+

|        0 0 0 0 0 1 0 1        |   <---第三次運算後,得數為5

+-------------------------------+

整個程式是乙個分治的思想。第一次我們把每相鄰的兩位加起來,得到每兩位裡1的個數,比如前兩位10就表示原數的前兩位有2個1。第二次我們繼續兩兩相加,10+01=11,00+10=10,得到的結果是00110010,它表示原數前4位有3個1,末4位有2個1。最後一次我們把0011和0010加起來,得到的就是整個二進位制中1的個數。程式中巧妙地使用取位和右移,比如第二行中$33333333的二進位制為00110011001100....,用它和x做and運算就相當於以2為單位間隔取數。shr的作用就是讓加法運算的相同數字對齊。

二分查詢32位整數的前導0個數

這裡用的c語言,我直接copy的hacker's delight上的**。這段**寫成c要好看些,寫成pascal的話會出現很多begin和end,搞得**很難看。程式思想是二分查詢,應該很簡單,我就不細說了。

int nlz(unsigned x)

if ((x >> 24) == 0)

if ((x >> 28) == 0)

if ((x >> 30) == 0)

n = n - (x >> 31);

return n;

}只用位運算來取絕對值

這是乙個非常有趣的問題。 

答案:假設x為32位整數,則x xor (not (x shr 31) + 1) + x shr 31的結果是x的絕對值,x shr 31是二進位制的最高位,它用來表示x的符號。如果它為0(x為正),則not (x shr 31) + 1等於$00000000,異或任何數結果都不變;如果最高位為1(x為負),則not (x shr 31) + 1等於$ffffffff,x異或它相當於所有數字取反,異或完後再加一。

高低位交換

這個題實際上是我出的,做為學校內部noip模擬賽的第一題。題目是這樣:

給出乙個小於2^32的正整數。這個數可以用乙個32位的二進位制數表示(不足32位用0補足)。我們稱這個二進位制數的前16位為「高位」,後16位為「低位」。將它的高低位交換,我們可以得到乙個新的數。試問這個新的數是多少(用十進位制表示)。

例如,數1314520用二進位制表示為0000 0000 0001 0100 0000 1110 1101 1000(新增了11個前導0補足為32位),其中前16位為高位,即0000 0000 0001 0100;後16位為低位,即0000 1110 1101 1000。將它的高低位進行交換,我們得到了乙個新的二進位制數0000 1110 1101 1000 0000 0000 0001 0100。它即是十進位制的249036820。

當時幾乎沒有人想到用一句位操作來代替冗長的程式。使用位運算的話兩句話就完了。

varn:dword;

begin

readln( n );

writeln( (n shr 16) or (n  shl 16) );

end.

而事實上,pascal有乙個系統函式swap直接就可以用。

二進位制逆序

下面的程式讀入乙個32位整數並輸出它的二進位制倒序後所表示的數。

輸入: 1314520    (二進位制為00000000000101000000111011011000)

輸出: 460335104  (二進位制為00011011011100000010100000000000)

varx:dword;

begin

readln(x);

x := (x and $55555555) shl  1 or (x and $aaaaaaaa) shr  1;

x := (x and $33333333) shl  2 or (x and $cccccccc) shr  2;

x := (x and $0f0f0f0f) shl  4 or (x and $f0f0f0f0) shr  4;

x := (x and $00ff00ff) shl  8 or (x and $ff00ff00) shr  8;

x := (x and $0000ffff) shl 16 or (x and $ffff0000) shr 16;

writeln(x);

end.

它的原理和剛才求二進位制中1的個數那個例題是大致相同的。程式首先交換每相鄰兩位上的數,以後把互相交換過的數看成乙個整體,繼續進行以2位為單位、以4位為單位的左右對換操作。我們再次用8位整數211來演示程式執行過程:

+---+---+---+---+---+---+---+---+

| 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |   <---原數

+---+---+---+---+---+---+---+---+

|  1 1  |  1 0  |  0 0  |  1 1  |   <---第一次運算後

+-------+-------+-------+-------+

|    1 0 1 1    |    1 1 0 0    |   <---第二次運算後

+---------------+---------------+

|        1 1 0 0 1 0 1 1        |   <---第三次運算後

+-------------------------------+

**matrix67部落格

位運算簡介及實用技巧(三) 高階篇 2

今天我們來看兩個稍微複雜一點的例子。n皇后問題位運算版 n皇后問題是啥我就不說了吧,學程式設計的肯定都見過。下面的十多行 是n皇后問題的乙個高效位運算程式,看到過的人都誇它牛。初始時,upperlim 1 shl n 1。主程式呼叫test 0,0,0 後sum的值就是n皇后總的解數。拿這個去交us...

位運算簡介及實用技巧(三) 高階篇 2

今天我們來看兩個稍微複雜一點的例子。n皇后問題位運算版 n皇后問題是啥我就不說了吧,學程式設計的肯定都見過。下面的十多行 是n皇后問題的乙個高效位運算程式,看到過的人都誇它牛。初始時,upperlim 1 shl n 1。主程式呼叫test 0,0,0 後sum的值就是n皇后總的解數。拿這個去交us...

位運算簡介及實用技巧(三) 高階篇 2

n皇后問題位運算版 n皇后問題是啥我就不說了吧,學程式設計的肯定都見過。下面的十多行 是n皇后問題的乙個高效位運算程式,看到過的人都誇它牛。初始時,upperlim 1 shl n 1。主程式呼叫test 0,0,0 後sum的值就是n皇后總的解數。拿這個去交usaco,0.3s,暴爽。proced...