你必須知道的基本位運算技巧(狀壓DP、搜索優化都會用到)


一. 位操做基礎

基本的位操做符有與、或、異或、取反、左移、右移這6種,它們的運算規則以下所示:加密


符號spa

描述 code

運算規則  htm

&ci

編譯器

兩個位都爲1時,結果才爲1it

|編譯

table

兩個位都爲0時,結果才爲0基礎

^   

異或

兩個位相同爲0,相異爲1

~   取反 0變1,1變0
<< 左移 各二進位所有左移若干位,高位丟棄,低位補0。1<<n等於2的n次方
>> 右移

各二進位所有右移若干位,對無符號數,高位補0,有符號數,各編譯器處理方法不同,有的補符號位(算術右移),有的補0(邏輯右移)。n>>2 等於 n/2的n次方

注意:

1.  在這6種操做符,只有~取反是單目操做符,其它5種都是雙目操做符。

2.  位操做只能用於整形數據,對float和double類型進行位操做會被編譯器報錯。


二. 經常使用位操做小技巧

簡單的位運算
1.’&’符號,x&y,會將兩個十進制數在二進制下進行與運算,而後 返回其十進制下的值。例如3(11)&2(10)=2(10)。
2.’|’符號,x|y,會將兩個十進制數在二進制下進行或運算,而後 返回其十進制下的值。例如3(11)|2(10)=3(11)。
3.’^’符號,x^y,會將兩個十進制數在二進制下進行異或運算,然 後返回其十進制下的值。例如3(11)^2(10)=1(01)。
4.’<<’符號,左移操做,x<<2,將x在二進制下的每一位向左移動兩位,最右邊用0填充,x<<2至關於讓x乘以4。
相應的,’>>’是右移操做,x>>1至關於給x/2,去掉x二進制下的最有一位。


三.技巧總結

    判斷一個整數是奇數仍是偶數:將該數與 1 相與,結果爲 0 則是偶數,結果爲 1 則是奇數。

    判斷第 n 位是不是 1:x & (1<<n),結果爲 1 說明是,爲 0 說明不是。

    將第 n 位設爲 1:y = x | (1<<n)

    將第 n 爲設爲 0:y = x & ~(1<<n)

    將第 n 位的值取反:y = x ^ (1<<n)

    將最右邊的 1 設爲 0:y = x & (x-1)

    分離最右邊的 1:y = x & (-x)

    將最右邊的 1 後面都設位 1:y = x | (x-1)

    分離最右邊的 0:y = ~x & (x+1)

    將最右邊的 0 設爲 1:y = x | (x+1)

    1. 判斷一個整數是奇數仍是偶數

    if ((x & 1) == 0) {
      x is even
    }
    else {
      x is odd
    }

    我十分確信每一個人都見過這個技巧。核心思路是若是整數的最低位 b0 是 1,那麼這個數就是奇數。這是由於 ‘x’ 的二進制表示中,b0 位只是能是 1 或者 0。將 ‘x’ 和 1 進行與運算能夠消除除了 b0 之外的其餘位。若是這個操做的結果是 0,那麼 ‘x’ 就是偶數,由於 b0 是 0
    ,不然 ‘x’ 是奇數。

    咱們來看一些例子。咱們選擇整數 43,它是一個奇數,二進制表示是 00101011,它的最低位 b0 是 1(重點)。如今將它和 1 相與:

    00101011
    &   00000001   (註釋: 1 的二進制是 00000001)
        --------
        00000001

    看到與運算是如何消除高位 b1-b7 而留下 b0 的嗎?結果是 1,這告訴咱們整數是奇數。

    如今咱們看一下 -43。舒適提示一下,獲取一個數的負數的二進制補碼的快速方法是用該數的絕對值取反加 1(注意符號位,正數省略了前面的 0)。因此 -43 的二進制是 11010101,能夠看到它的最低位還是 1,而且這個數是奇數(敲黑板:若是咱們不使用補碼的話,就不是這樣)。

    咱們再來看下 98,它的二進制碼爲 1100010

    01100010
    &   00000001
        --------
        00000000

    與運算後的結果是 0,這表示 98 的 b0 位是 0,因此是偶數。
    再試一下負數 -98,二進制碼爲 10011110,b0 位是 0,與運算後的結果是 0,說明 -98 是偶數。

    2. 判斷第 n 位是不是 1

    if (x & (1<<n)) {
      n-th bit is set
    }
    else {
      n-th bit is not set
    }

    在上一個技巧中,咱們經過 x&1 判斷第一位是不是 1。這個技巧對此進行了改善,能夠判斷任意位是不是 1。它的原理是,將 1 左移 n 位,而後與給定的數進行與運算,這將會消除除了第 n 位以外的其餘位。

    下面是 1 進行左移的結果:

    1         00000001    (same as 1<<0)
    1<<1      00000010
    1<<2      00000100
    1<<3      00001000
    1<<4      00010000
    1<<5      00100000
    1<<6      01000000
    1<<7      10000000

    若是咱們將 ‘x’ 與左移 n 位的 1 進行與運算,就能夠有效的排除 ‘x’ 中除了第 n 位以外的其餘位。若是運算的結果是 0,說明第 n 位是 0,不然是 1。

    咱們來看一些具體例子。

    122 的第三位是 1 嗎?咱們要執行的操做是:122 & (1<<3)
    122 的二進制碼是 011110101<<3 的結果是 00001000

    01111010
    &   00001000
        --------
        00001000

    能夠看到結果不是 0,說明 122 的第三位是 1。

    注意:在個人文章中,位從 0 開始計數。

    那 -33 呢?它的第 5 位是 1 嗎?

    11011111      (-33 in binary)
    &   00100000     (1<<5)
        --------
        00000000

    結果是 0,因此第 5 位不是 1。

    3. 將第 n 位設爲 1

    y = x | (1<<n)

    這個技巧一樣使用了移位運算(1<<n)來將第 n 位置爲1,只是將前面的 & 替換成了 |。將一個變量和第 n 位爲 1 的數進行或運算的結果是該變量的第 n 位會被置爲 1。這是由於任何數與 0 相或,結果是 0;與 1 相或結果位 1()。讓咱們來看看這是如何工做的:

    假設咱們有一個數是 120,而且想把它的第 2 位置爲 1。

    01111000    (120 in binary)
    |   00000100    (1<<2)
        --------
        01111100

    若是將 -120 第 6 位置爲 1 呢?

    10001000   (-120 in binary)
    |   01000000   (1<<6)
        --------
        11001000

    4. 將第 n 爲設爲 0

    y = x & ~(1<<n)

    這個技巧的核心是 ~(1<<n),它除了第 n 位是 0,其餘位都是 1。

    它看起來像這樣:

    ~1        11111110  (same as ~(1<<0))
    ~(1<<1)   11111101
    ~(1<<2)   11111011
    ~(1<<3)   11110111
    ~(1<<4)   11101111
    ~(1<<5)   11011111
    ~(1<<6)   10111111
    ~(1<<7)   01111111

    將其與變量 ‘x’ 相與的結果是 ‘x’ 的第 n 位會置 0。不管這個數的第 n 位是 0 仍是1,和 0 相與的結果都是 0。

    這是一個例子,讓咱們把 127 的第 4 位設爲 0:

    01111111    (127 in binary)
    &   11101111    (~(1<<4))
        --------
        01101111

    5. 將第 n 位的值取反

    y = x ^ (1<<n)

    這個技巧仍然是「設置第 n 位的值」,可是此次是與變量 ‘x’ 進行異或運算。若是兩個數的每一位都相同,異或的結果是 0,否者是 1。那它是如何使第 n 位取反的呢?若是第 n 位是 1,那麼與 1 進行異或運算的結果是 0;相反地,若是第 n 位是 0,那麼和 1 進行異或的結果是 1。看到沒,位會被翻轉。

    下面這個例子將 01110101 的第 5 位取反:

    01110101
    ^   00100000
        --------
        01010101

    若是其餘位相同,可是第 5 位是 0 呢?

    01010101
    ^   00100000
        --------
        01110101

    注意到了嗎?執行兩次異或運算後返回最遲的值。這個漂亮的異或運算用來計算 RAID 矩陣的奇偶性和簡單的加密計算,更多內容見其餘文章。

    6. 將最右邊的 1 設爲 0

    y = x & (x-1)

    如今終於變得更加有趣!!!說實話前面 5 個技巧有點無聊。

    這個技巧將將最右邊的 1 設爲 0。例如,給出一個整數 00101010(最右邊的 1 被加粗),結果變爲 00101000。或者將 00010000 變爲 0,由於它只有一個 1。

    下面是更多的例子:

    01010111    (x)
    &   01010110    (x-1)
        --------
        01010110
    
        01011000    (x)
    &   01010111    (x-1)
        --------
        01010000
    
        10000000    (x = -128)
    &   01111111    (x-1 = 127 (with overflow))
        --------
        00000000
    
        11111111    (x = all bits 1)
    &   11111110    (x-1)
        --------
        11111110
    
        00000000    (x = no rightmost 1-bits)
    &   11111111    (x-1)
        --------
        00000000

    爲何這樣能夠呢?

    若是你仔細觀察並思考上面的例子,能夠發現有兩種狀況:

    1. 最右邊存在值爲 1 的位。在這種狀況下,減一會使比該位低的位的值變爲 1,而自身變爲 0(因此若是你加一,能夠獲得原來的值)。這一步已經屏蔽了最右邊的 1,接着將它與原來的值進行與運算,最右邊的 1 就被設爲了 0。
    2. 最右邊不存在值爲 1 的位(全都是 0)。這種狀況下減一,值會溢出,全部的位被設位 1,將其和 0 相與,結果爲 0。

    7. 分離最右邊的 1

    y = x & (-x)

    這個技巧找出最右邊的 1 並將其餘爲設爲 0。例如:01010100 會變爲 00000100。

    下面是一些例子:

    10111100  (x)
    &   01000100  (-x)
        --------
        00000100
    
        01110000  (x)
    &   10010000  (-x)
        --------
        00010000
    
        00000001  (x)
    &   11111111  (-x)
        --------
        00000001
    
        10000000  (x = -128)
    &   10000000  (-x = -128)
        --------
        10000000
    
        11111111  (x = all bits one)
    &   00000001  (-x)
        --------
        00000001
    
        00000000  (x = all bits 0, no rightmost 1-bit)
    &   00000000  (-x)
        --------
        00000000

    這個技巧的利用了二進制補碼。在二進制補碼系統中,-x 等於 ~x+1。如今咱們來檢查兩種可能的狀況:

    1. 最右邊存在值爲 1 的位 b(i)。咱們將全部的位分紅兩部分,b(i)右邊位b(i-1),b(i-2),b(0),左邊位 b(i+1),...,b(n)。如今咱們來計算 -x:首先將 x 取反,則 b(i) 爲 0 ,b(i-1),...,b(0) 爲 1,並反轉了 b(i+1),...,b(n)。接着執行加 1,則 b(i-1),b(i-2),...,b(0) 變爲 0,b(i) 變爲 1。最後,與原始的 x 相與,則 bi 左邊所有爲 0 (高位取反),bi右邊也全爲 0,只有 bi 爲 1。
    2. 最右邊不存在 1。值爲 0 ,0 的二進制補碼還是 0,0&0=0,沒有位會被設爲 1。

    8. 將最右邊的 1 後面都設位 1

    y = x | (x-1)

    例如:01010000 變爲 01011111。

    這個技巧並不完善,由於 0 會變成 1,而 0 中沒有 1。

    10111100  (x)
    |   10111011  (x-1)
        --------
        10111111
    
        01110111  (x)
    |   01110110  (x-1)
        --------
        01110111
    
        00000001  (x)
    |   00000000  (x-1)
        --------
        00000001
    
        10000000  (x = -128)
    |   01111111  (x-1 = 127)
        --------
        11111111
    
        11111111  (x = -1)
    |   11111110  (x-1 = -2)
        --------
        11111111
    
        00000000  (x)
    |   11111111  (x-1)
        --------
        11111111
    1. 不存在 1。這種狀況下 x=0,而且 x-1 等於 -1。-1 的二進制補碼爲 11111111,將其與 0 相或獲得的結果爲 1。
    2. 存在 1。參考第 6 個技巧,x-1 會將最右邊的 1 設爲 0,並將該位後面的位設位 1,這裏和原來的值進行或運算,該位後面的位也會被設位 1,而且由於本來該位是 1 ,如今是 0,或運算後結果仍是 1。

    9. 分離最右邊的 0

    y = ~x & (x+1)

    這個技巧和第 7 個是相反地,它找到最右邊的 0,將其值設爲 1 並將其餘爲設置 0。

    10111100  (x)
        --------
        01000011  (~x)
    &   10111101  (x+1)
        --------
        00000001
    
        01110111  (x)
        --------
        10001000  (~x)
    &   01111000  (x+1)
        --------
        00001000
    
        00000001  (x)
        --------
        11111110  (~x)
    &   00000010  (x+1)
        --------
        00000010
    
        10000000  (x = -128)
        --------
        01111111  (~x)
    &   10000001  (x+1)
        --------
        00000001
    
        11111111  (x = no rightmost 0-bit)
        --------
        00000000  (~x)
    &   00000000  (x+1)
        --------
        00000000
    
        00000000  (x)
        --------
        11111111  (~x)
    &   00000001  (x+1)
        --------
        00000001

    證實:假設最右邊存在 0。~x 將最右邊的 0 設置 1,x+1 也同樣。接着將 ~xx+1 相與,比該位高的都會被設爲 0 (由於取反了,高位變了,而x+1操做不會修改高位),x+1 將比該位低的都設位 0,而後將該位設爲 1,因此相與後,比該爲低的都會被設爲 0。

    10. 將最右邊的 0 設爲 1

    y = x | (x+1)

    例如:10100011 變成 10100111。

    10111100  (x)
    |   10111101  (x+1)
        --------
        10111101
    
        01110111  (x)
    |   01111000  (x+1)
        --------
        01111111
    
        00000001  (x)
    |   00000010  (x+1)
        --------
        00000011
    
        10000000  (x = -128)
    |   10000001  (x+1)
        --------
        10000001
    
        11111111  (x = no rightmost 0-bit)
    |   00000000  (x+1)
        --------
        11111111
    
        00000000  (x)
    |   00000001  (x+1)
        --------
        00000001

    證實:將 x 和 x+1 相或不會丟失任何信息。x+1使最右邊的 0 變成 1,最右邊 0 右邊的結果爲 0。二者相或的結果爲 max{x, x+1},而 x+1最右邊的 0 被設爲了 1。

    相關文章
    相關標籤/搜索