1. 程式人生 > >位運算與二進制壓縮

位運算與二進制壓縮

二進制表示 類型 自動 異或 數組 效率 sig 加減法 壓縮

位運算

bit是度量信息的單位,包含0和1兩個狀態。計算機的各種運算最後無不歸結為一個個bit的變化。
對於《算法競賽進階指南》的章節目錄,是以0x00~0xFF這些由數組0~9與字母A~F表示的2位十六進制整數進行編號的,其中"0x"表示十六進制。第一章由0x00開始,前言分配序號0xFF,後記分配序號0x7F。這就是以最高二進制位為正負號位的“補碼”形式表示的8位二進制數。在C++中,8位二進制數對應char類型,範圍為-128~127,其中0xFF代表-1, 0x7F代表最大值127
與 或 非 異或
ans, & or, | not, ~ xor, ^
基本運算:
1.按位與(&)
位運算實質是將參與運算的數字轉換為二進制,而後逐位對應進行運算。
按位與運算為:兩位全為1,結果為1,即1 & 1 = 1,1 & 0 = 0,0 & 1 = 0,0 & 0 = 0。
(1)與0相與可清零。
(2)與1相與可保留原值,可從一個數中取某些位。例如需要取10101110中的低四位,10101110 & 00001111 = 00001110,即得到所需結果。
2.按位或(|)
兩位只要有一位為1,結果全為1。0 | 1 = 1, 1 | 0 = 1, 1 | 1 = 1, 0 | 0 = 0
(1)與0相或可保留原值
(2)與1相或可將對應位置變成1,例如:將x=10100000的低四位置1,使x | 00001111 = 10101111即可。
3.異或(^)
兩位不同為1,相同則為0。0 ^ 1 = 0, 1 ^ 0 = 0, 1 ^ 1 = 1, 0 ^ 0 = 0
(1)使指定位翻轉:找一個數,對應X要翻轉的各位為1,其余為0,使其與x進行異或運算即可。例如:x=10101110,使最低四位翻轉,x ^ 00001111 = 10100001。
(2)交換兩變量的值(比借助容器法、加減法效率高),原理:一個數對同一個數連續兩次進行異或運算,結果與這個數相等(非常顯然的)。交換方法:a = a ^ b,b = a ^ b,a = a ^ b。
4.非(取反(~))
將一個數按位取反,即~0 = 1,~1 = 0。
這些符號不局限於邏輯運算,均可作用於二進制整數。為了避免混淆,統一用單詞xor表示異或,而用^表示乘方
在m為二進制數中,為方便起見,通常稱最低位為第0位,從右到左一次類推,最高位位m-1位。因此默認使用這種表示方式來指明二進制數以及整數在二進制表示下的位數
補碼:
32位無符號整數unsigned int:
直接把這32位編碼c看做32位二進制數N
32位有符號整數int:
以最高位為符號位,0表示非負數,1表示負數。
對於最高位為0的每種編碼C,直接看做32位二進制數S
同時,定義該編碼按位取反後得到的編碼~C表示-1-S

技術分享圖片

可以發現補碼下每個數值都有唯一的表示方式,並且任意兩個數值做加減法運算,都等價於32位補碼下做最高位不進位的二進制加減法運算。發生算術溢出時,32位無符號整數相當於自動對2^32取模。而有符號整數則會因為溢出到符號位使符號位變為1而出現負數。

(實際上半段基本都是《算法競賽進階指南》筆記)

二進制壓縮

二進制狀態壓縮,是指將一個長度為m的bool數組用一個m位二進制整數表示並儲存的方法。利用下列位運算操作可實現原bool數組中對應下標元素的存取

取出整數n在二進制表示下第k位 (n >> k) & 1
取出整數n在二進制表示下的第0~k-1位(後k位) n & ((1 << k) - 1)
把整數n在二進制表示下的第k位取反 n xor (1 << k)
對整數n在二進制表示下的第k位賦值1 n | (1 << k)
對整數n在二進制表示下的第k位賦值為0 n & (~(1 << k))

這種方法運算簡便,節省了程序運行的時間和空間。
當m不太大時,可以直接使用一個整數類型存儲。當m較大時,可以使用若幹個整數類型(int數組),也可以直接使用stl中的bitset實現

例題1 :最短hamilton路徑
給定一張n(n <= 20)個點的帶權無向圖,點從0~n-1標號,求起點0到終點n-1的最短Hamilton路徑
(Hamilton路徑的定義是從0到n-1不重不漏地經過每個點恰好一次)
暴力做法:枚舉n個點的全排列,計算路徑長度取最小值,時間復雜度為O(n * n!),使用狀態壓縮DP可以優化到O(n^2 * 2^n)
在任意時刻如何表示哪些點已經被經過,哪些點沒有被經過,可以使用一個n位二進制數,若其第i位為1,則表示第i個點已經被經過,反之未被經過。在任意時刻還需要知道當前處在哪一個位置
我們可以使用f[i, j](0 <= i <=2^n, 0 <= j < n)表示“點被經過的狀態”對應的二進制數為i,且目前處於點j時的最短路徑
在起點時,有f[1, 0] = 0,即只經過點0(i只有第0位為1),目前處於起點0,最短路長度為0。方便起見,我們把f數組其他值設為無窮大。最終目標是f[(1 <<n) - 1, n - 1]即經過所有點(i的所有位都是1),處於終點n-1的最短路
在任意時刻,有公式f[i, j] = min{f[i ^ (1 << j), k] + weight(k, j)},其中0 <= k < n並且((i >>j) & 1) = 1,即當前時刻“被經過的點的狀態”對應的二進制數為i,處於點j。因為j只能被恰好經過一次,所以一定是剛剛經過的,故在上一時刻“被經過的點的狀態”對應的二進制數的第j位應該賦值為0,也就是i ^ (1 << j)。另外,上一時刻所處的位置可能是i ^ (1 <<j)中任意一個是1的數位k,從k走到j需要經過weight(k, j)的路程,可以考慮所有這樣的k取最小值。

 1 int f[1 << 20][20];
 2 inline int hamilton(int n, int weight[20][20]) {
 3     memset(f, 0x3f, sizeof(f));
 4     f[1][0] = 0;
 5     for(int i = 1; i < 1 << n; ++i) 
 6         for(int j = 0; j < n; ++j) 
 7             if(i >> j & 1) 
 8                 for(int k = 0; k < n; ++k)
 9                     if(i >> k & 1)
10                         f[i][j] = min(f[i][j], f[i ^ 1 << j][k] + weight[k][j]);
11     return f[(1 << n) - 1][n - 1];
12 } 

位運算與二進制壓縮