>>最近了解了一些題目,其中對棋盤或者是漢諾塔的表示通常都用到了狀態壓縮的方法,配合BFS或者是DP來作。算法
題目連接:數組
漢諾塔移動 http://stackoverflow.com/questions/16601701/facebook-sample-puzzle-towers-of-hanoi數據結構
黑白棋遊戲 http://www.wikioi.com/problem/2743/優化
作過USACO的同窗會知道有不少搜索和DP均可以用狀態壓縮優化。
通常來說,若是狀態的夠成很是多,但每一個構成相對簡單,就能夠狀態壓縮,好比巨大的0/1矩陣等。
固然若是狀態壓縮,提取每一個數據起來就會耗費更多的時間,因此通常運用在狀態複製量較大,比較量較小的狀況下。
舉例:
多關鍵字排序能夠用狀態壓縮,如A,B,C三個關鍵字,均小於100,能夠壓縮成A*10000+B*100+C,直接比較便可,很是方便。
N皇后的狀態壓縮,配合位運算,神速。
相鄰兩行之間的匹配關係,壓成二進制,作DP。
其實只要想節約空間均可以用狀態壓縮。
壓縮通式:0<a<A 0<b<B 0<c<C (關鍵程度a>=b>=c) <==> T=a*B*C+b*C+c ,A*B*C<maxstruct
爲了方便通常使A=B=C,轉換成A進制數便可
(*)狀態壓縮的效率:spa
1.空間利用率:顯然按十進制壓縮的空間效率過低,能夠考慮按50爲一個單元壓縮,
2.時間效率:提取時大量的取模和除法運算使效率過低,能夠考慮換成64進制的數,進行壓縮。
這裏的空間又有了必定程度上的浪費,也體現了時間與空間的辯證關係。設計
關於狀態壓縮動態規劃:排序
引入
首先來講說「狀態壓縮動態規劃」這個名稱,顧名思義,狀態壓縮動態規劃這個算法包括兩個特色,第一是「狀態壓縮」,第二是「動態規劃」。
狀態壓縮:
從狀態壓縮的特色來看,這個算法適用的題目符合如下的條件:
1.解法須要保存必定的狀態數據(表示一種狀態的一個數據值),每一個狀態數據一般狀況下是能夠經過2進制來表示的。這就要求狀態數據的每一個單元只有兩種狀態,好比說棋盤上的格子,放棋子或者不放,或者是硬幣的正反兩面。這樣用0或者1來表示狀態數據的每一個單元,而整個狀態數據就是一個一串0和1組成的二進制數。
2.解法須要將狀態數據實現爲一個基本數據類型,好比int,long等等,即所謂的狀態壓縮。狀態壓縮的目的一方面是縮小了數據存儲的空間,另外一方面是在狀態對比和狀態總體處理時可以提升效率。這樣就要求狀態數據中的單元個數不能太大,好比用int來表示一個狀態的時候,狀態的單元個數不能超過32(32位的機器)。
這裏舉一個能夠狀態壓縮的例子,好比poj上的第1753題Flip Game(見poj1753解題報告),雖然這道題目不是用動態規劃作,可是能夠用狀態壓縮,4×4的格子,每一個格子的狀態爲黑或者白,這樣就能夠用一個16位的二進制數來表示,在實現的時候能夠用一個int類型來表示這個二進制數。在狀態和狀態對比和轉換的時候能夠用位操做來完成。
位操做實現技巧:
若是要得到第i位的數據,判斷((data&(0X1<<i))==0),若真,爲0,假,爲1;
若是要設置第i位爲1,data=(data|(0X1<<i));
若是要設置第i位爲0,data=(data&(~(0X1<<i)));
若是要將第i位取反,data=(data^(0X1<<i);
若是要取出一個數的最後一個1(lowbit):(data&(-data)) (這裏利用的是負數取反加1實際上改變的是二進制最低位的1這個性質)遞歸
插一句:負數的補碼錶示:(負數的補碼等於對應的正數二進制表示取反加1)遊戲
在從4中減去12時,究竟進行了什麼操做?其實是對負二進制數值採用了2的補碼形式。這裏須要作一個約定,以免解釋它爲何有效。下面看看如何從正數中構建負數的2的補碼形式,讀者也能夠本身證實這是有效的。如今回到前面的例子,給-8構建2的補碼形式。首先把+8轉換爲二進制:
0000 1000
如今反轉每一個二進制數字,即把0變成1,把1變成0: 1111 0111
這稱爲1的補碼形式,若是給這個數加上1,就獲得了2的補碼形式: 1111 1000
這就是從+4中減去+12,獲得的–8的二進制表示。爲了確保正確,下面對–8和+12進行正常的相加操做:
+12轉換爲二進制 0000 1100 –8轉換爲二進制 1111 1000
把這兩個數加在一塊兒,獲得: 0000 0100ip
動態規劃:
若是說狀態壓縮是數據結構的話,那麼動態規劃應該是算法了。題目經過動態規劃來解一般有兩個動機,第一是利用遞歸的重疊子問題,進行記憶話求解,即遞歸法的優化。第二是把問題看做多階段決策的過程來求解問題。在狀態壓縮動態規劃中咱們討論的是第二種動機。
多階段決策過程求解問題的動態規劃最重要的是劃分階段和找到狀態轉移方程。對於劃分階段,是根據不一樣階段之間的獨立性來劃分,一般會用狀態數組的第一個下標來記錄這個階段的標記(好比01揹包問題中的狀態數組第一個下標爲物品的個數,棋盤放棋子問題中的狀態數組的第一個下標爲棋盤的行數等等)。另外一個重要的即是狀態轉移方程,狀態轉移方程是遞推時獲得一個狀態數據的重要根據。一般狀況下狀態數組的除了第一個下標之外都是表示狀態數據的,而狀態數組的值是和所求結果緊密結合的。在後面的幾個例題中會重點說明狀態轉移方程。
當狀態壓縮和動態規劃結合的時候便造成了一類問題的一種算法,即狀態壓縮動態規劃的算法。這種算法最多見在棋盤問題上或者網格問題上,由於這一類問題的狀態數據的單元較少,能夠經過狀態壓縮來對當前棋盤或者網格的狀態進行處理。
例題解析
例:在n*n(n≤20)的方格棋盤上放置n 個車,某些格子不能放,求使它們不能互相攻擊的方案總數。
分析:
首先看到這道題目,咱們可能會想到8皇后問題,用深度優先搜索。可是這裏放這道題目是用來講明此題能夠狀態壓縮動態規劃,並且狀態壓縮動態規劃相比於深度優先搜索能夠應對更多的變化狀況和擁有更高的效率。
用狀態壓縮動態規劃算法
1.劃分階段,本題比較簡單,以行來劃分階段,即一行一行的放車。
2.找狀態轉移方程,由於前i行的狀態是根據前i-1行的狀態來肯定的,因此,在狀態數組中要記錄多行狀態。故設計狀態數組爲f[i][x],i表示這是前i行的狀態,x在這裏是就是一個壓縮的狀態,記錄一個二進制數從而來表示前i行的狀態,f的值記錄造成前i行的x狀態有多少種方案。狀態轉移方程是f[i][a]=SUM{f[i-1][b]},下面討論a狀態與b狀態的關係。先舉個例子,若是a狀態爲01011,代表前i行中的第0,1,3列都放置了一個車(從右往左看)。因而b的狀態就多是01010,01001,00011三種狀態,因而f[3][01011]=f[2][01010]+f[2][01001]+f[2][00011]。再看廣泛的狀況,a-b的值的二進制表示中只有一個1。並且要保證a狀態,b狀態不能和非可用的格子衝突。綜上,狀態轉移方程爲:
f[i][a]=SUM{f[i-1][b]} (a-b的值的二進制表示中只有一個1; a,b不與題目中的約束條件衝突)
這樣經過遞推能夠獲得f[n][1…1]的方案數即爲最後的方案總數。
算法總結:
1.判定這道題目能夠用數據壓縮動態規劃來作。重點是看它的特色,是否符合狀態壓縮動態規劃的條件。
2.劃分階段。像棋盤和網格問題大多數是一行一行的進行操做,故以行來劃分階段,固然也有其餘的階段劃分方法,具體問題具體分析。
3.找狀態轉移方程
3.1設計狀態數組。一般數組的第一個參數爲階段的標誌,其餘幾個參數爲記錄狀態用(若是第i行的狀態能夠經過第i-1行的狀態來肯定,則須要一個參數,即第i行的狀態;若是像炮兵陣地那題同樣,第i行的狀態須要根據第i-1行和第i-2行來肯定,則須要兩個參數,分別爲第i行的狀態和第i-1行的狀態,具體見附錄)。數組的值與結果掛鉤,一般有如下幾種狀況:a.題目要求一共有多少的方案,這時數組的值爲當前狀態的方案數。(好比poj2411以及例題)b.題目要求最佳方案,這是數組的值爲最佳方案的值。(好比poj1185炮兵陣地)。
3.2列出狀態轉移方程,對應與上面的a,b兩種狀況,a狀況時,狀態轉移方程爲f[i][a]=sum{f[i-1][b]}; b狀況時,狀態轉移方程爲f[i][a]=max{f[i][b]}+sth.
3.3找出狀態轉移方程中a,b之間的關係。即爲狀態轉移方程添加約束條件。
4.在不少狀況下須要對每個階段的可能值進行dfs來找出全部的可能值存儲起來,以便在對每個階段處理的時候可以很快的運用以提升效率。