位運算是不少算法優化的基礎和實現的條件,極其重要。理解位運算對於一些算法及其優化有着很是重要的意義。本篇隨筆講解位運算的一些基本原理和經常使用的使用技巧。算法
注:本篇隨筆的全部「運算」均指二進制下的運算,請你們自行理解。數組
兩個二進制數進行與&運算,若是對應位都爲1則結果爲1,不然爲0.優化
與運算經常用於二進制下的取位操做。想要知道二進制下的某位是不是1,就&上這個位數對應的十進制數。假如返回的是這個十進制數自己,則這個位的確是1,反之就是0.spa
好比:code
咱們要取第三位是否爲1,咱們只須要與&上第三位(二進制表示爲100)對應的二進制數4,若是返回值爲4,就表明第三位爲1,反之就是0.數學
最經常使用的是取二進制下的最末位,即a&1。這樣的技巧能夠用於判斷奇偶,根據二進制常識,尾數爲1則爲奇數,反之爲偶數。it
兩個二進制數進行或|運算,若是對應位有一個爲1,結果就爲1.只有在兩個數的對應位置都是0的時候,結果才爲0.class
或運算經常使用於二進制特定位的賦值。想把哪一個位強行變成1,就用這個數|上這個位數對應的二進制數。效率
仍是上面那個例子,咱們想讓00000的第三位變成1.即十進制變4,咱們直接|上4就能夠。基礎
固然,不一樣於&運算,咱們不多用|運算進行任意位賦值。一般來說,咱們只使用a|1把a的最後一位強行變成1,其實質意義是把原數加一。或者使用a|1-1再把它變爲0.這個技巧一般用於把它變成它最接近的偶數。
兩個二進制數進行異或(^)運算,若是對應位相同,不論是0或者是1,都返回1,反之返回0.
其實沒啥用途...
好吧,我介紹一個性質:一個數通過兩次異或以後等於原數。
(很好理解)
把給定二進制數所有取反。
其實沒什麼運算上的用途,本蒟蒻曾看見一些大佬用這個運算判斷輸入是否爲0...
大約長這個樣子:
while(~scanf("%d",&n))
a<<b表示把a的二進制位向左移動b位,低位用0補上。
根據二進制的常識,咱們會發現,二進制第k位上的數就等於\(2^k\)。(從0開始計位)
好比,二進制下的100就是\(2^{k=2}=4\)。
因此咱們發現,左移運算a<<b的實質就是\(a×2^b\)。
左移運算最經常使用的技巧就是用來代替×2的整數次冪的乘法運算。由於咱們廣泛認爲,位運算是要比四則運算加減乘除及模運算更快一些的運算。
a>>b就是把a的二進制位向右移動b位,溢出的捨去。
類比於左移運算,咱們發現右移運算就是把a除以2的整數次冪。這就是右移運算的用途——優化除法運算。
這裏須要特殊說明的是,右移算法能夠用在數學知識中的求最大公約數的程序塊上。由於mod運算的效率慢的出奇,因此咱們能夠用右移運算來進行除以2的操做。聽說能夠提升百分之60的效率。
位運算的優先級是咱們在處理位運算的時候經常要考慮的問題,誠然,咱們能夠用括號強制位運算的順序,可是,咱們仍是應該學會位運算的優先級(這應該是常識)。
位運算的優先級以下:
按位反(~)>位移運算(<<,>>)>按位與(&)>按位異或(^)>按位或(|)
衆所周知,狀壓DP就是把狀態壓縮成一個01串(其實就是一個二進制數),用以減小DP數組的維數。可是咱們在DP的時候就要按照01串來進行狀態的轉移。因此位運算是狀壓DP的基礎知識和必備知識。因此我在本篇隨筆的末尾還附上了狀壓DP中比較經常使用的操做及其二進制實現的方式。
正文:(本文中的a表示十進制下的整數)
一、得到第i位的數字:(a>>i)&1 或者 a&(1<<i)
很好理解,咱們知道能夠用&1來提取最後一位的數,那麼咱們如今要提取第i位數,就直接把第i位數變成最後一位便可(直接右移)。或者,咱們能夠直接&上1左移i位,也能達到咱們的目的。
二、設置第i位爲1:a=a|(1<<i)
咱們知道強制賦值用|運算,因此就直接強制|上第i位便可。
三、設置第i位爲0:a=a&(~(1<<i))
這裏比較難以理解。其實很簡單,咱們知道非~運算是按位取反,(1<<i)非一下就變成了第i爲是0,其它全是1的二進制串。這樣再一與原數進行&運算,原數的第i位不管是什麼都會變成0,而其餘位不會改變(實在不明白的能夠用紙筆進行推演)。
四、把第i位取反:a=a^(1<<i)
1左移i位以後再進行異或,咱們就會發現,若是原數第i位是0,一異或就變成1,不然變成0。
五、取出一個數的最後一個1:a&(-a)
學過樹狀數組的同窗會發現,這就是樹狀數組的lowbit。事實上,這和樹狀數組的原理是同樣的。我想,不須要我多解釋。