狀壓DP(超詳細!!!)

1、定義

總述

狀態壓縮動態規劃,就是咱們俗稱的狀壓DP,是利用計算機二進制的性質來描述狀態的一種DP方式。 算法

不少棋盤問題都運用到了狀壓,同時,狀壓也很常常和BFSDP連用。 spa

狀壓dp其實就是將狀態壓縮成2進制來保存 其特徵就是看起來有點像搜索,每一個格子的狀態只有1或0 ,是另外一類很是典型的動態規劃blog

舉個例子:有一個大小爲n*n的農田,咱們能夠在任意處種田,如今來描述一下某一行的某種狀態: 搜索

設n = 9;遍歷

有二進制數 100011011(九位),每一位表示該農田是否被佔用,1表示用了,0表示沒用,這樣一種狀態就被咱們表示出來了:見下表二進制

因此咱們最多隻須要 2^(n + 1) - 1的十進制數就好(二進制形式是n個1) 如今咱們有了表示狀態的方法,但內心也會有些不安:上面用十進制表示二進制的數,枚舉了所有的狀態,DP起來複雜度豈不是很大?沒錯,狀壓實際上是一種很暴力的算法,由於他須要遍歷每一個狀態,因此將會出現2^n的狀況數量,不過這並不表明這種方法不適用:一些題目能夠依照題意,排除不合法的方案,使一行的總方案數大大減小從而減小枚舉方法

爲了更好的理解狀壓dp,首先介紹位運算相關的知識。 im

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

1.判斷一個數字x二進制下第i位是否是等於1。(最低第1位)數據

方法:if(((1<<(i−1))&x)>0) 將1左移i-1位,至關於製造了一個只有第i位 上是1,其餘位上都是0的二進制數。而後與x作與運算,若是結果>0, 說明x第i位上是1,反之則是0。 img

2.將一個數字x二進制下第i位更改爲1。

方法:x=x|(1<<(i−1)) 證實方法與1相似。

3.將一個數字x二進制下第i位更改爲0。

方法:x=x&~(1<<(i−1))

4.把一個數字二進制下最靠右的第一個1去掉。

方法:x=x&(x−1)

2、典型例題

【例題1】騎士(P1896 [SCOI2005]互不侵犯)

題目描述

在 n×n(1<=n<=10) 的棋盤上放 k(0<=k<n×n)個國王,國王可攻擊相鄰的 8 個格子,求使它們沒法互相攻擊的方案總數。

輸入格式

輸入有多組方案,每組數據只有一行,包含兩個整數 n 和 k。

輸出格式

每組數據一行爲方案總數,若不可以放置則輸出 0。

輸入樣例

3 2

4 4

樣例輸出

16

79


實際狀壓dp顧名思義,就是採用位運算,來記錄更多的必須記錄的狀態來作dp有了比較深的dp功底後只要對位運算有了解就能夠解決問題。。。

考慮到每行每列之間都有互相的約束關係。所以,咱們能夠用行和列做爲另外一個狀態的部分。用一個新的方法表示行和列的狀態:數字。考慮任何一個十進制數均可以轉化成一個二進制數,而一行的狀態就能夠表示成這樣——例如:1010(2)

就表示:這一行的第一個格子沒有國王,第二個格子放了國王,第三個格子沒有放國王,第四個格子放了國王。而這個二進制下的數就能夠轉化成十進制: 10(10)

因而,咱們的三個狀態就有了:第幾行(用i表示)、此行放什麼狀態(用j表示)、包括這一行已經使用了的國王數(用s表示)。

考慮狀態轉移方程。咱們預先處理出每個狀態(s[x])其中包含二進制下1的個數,及此狀態下這一行放的國王個數(num[x]),因而就有:

f[i][j][s]=sum(f[i−1][k][s−num[j]]),f[i][j][s]就表示在只考慮前i行時,在前i行(包括第i行)有且僅有s個國王,且第i行國王的狀況是編號爲j的狀態時狀況的總數。而k就表明第i-1行的國王狀況的狀態編號

 

【例題2】牧場的安排(P1879 [USACO06NOV]玉米田Corn Fields)

Farmer John 新買了一塊長方形的牧場,這塊牧場被劃分紅 M列 N 行  (1≤M≤12;1≤N≤12),每一格都是一塊正方形的土地。FJ 打算在牧場上的某幾格土地裏種上美味的草,供他的奶牛們享用。遺憾的是,有些土地至關的貧瘠,不能用來放牧。而且,奶牛們喜歡獨佔一塊草地,因而 FJ 不會選擇兩塊相鄰的土地,即:沒有哪兩塊草地有公共邊。固然,FJ 尚未決定在哪些土地上種草。 做爲一個好奇的農場主,FJ 想知道,若是不考慮草地的總塊數,那麼,一共有多少種種植方案可供他選擇。固然,把新的牧場荒廢,不在任何土地上種草,也算一種方案。請你幫 FJ 算一下這個總方案數。

輸入格式

第 1行:兩個正整數 M 和 N,用空格隔開; 第 2到 M+1行:每行包含 N 個用空格隔開的整數,描述了每塊土地的狀態。輸入的第 i+1行描述了第 i行的土地。全部整數均爲 0 或 1,1 表示這塊土地足夠肥沃,0 則表示這塊地上不適合種草。

輸出格式

第 1 行:輸出一個整數,即牧場分配總方案數除以 10^8的餘數。

樣例輸入

2 3

1 1 1

0 1 0

樣例輸出

9


 題目大意

給N*M的棋盤,每一個格子不是0就是1,1表明能夠種草,不然不能。相鄰兩個格子不能同時種草,求種草的方案總數。

思路

狀態壓縮類動態規劃,狀壓dp通常會有明顯的數據範圍特徵,即n,m通常都在20之內。可將每一排的N個當作一個N位二進制,先預處理出每一行能夠運行的狀態,這樣能夠去掉不少無效狀態(如110),而後DP處理,枚舉當前有效狀態和上一行有效狀態的關係。

f[i][j] 表示第i行在狀態j的時候的方案數,其中j咱們用一個二進制數來表示。

轉移的時候只要判斷與當前行和上一行是否衝突便可,若是不衝突,分f[i][j]=∑f[i−1][k]其中k爲不衝突的狀態。Ans=∑1≤i≤numf[n][i] 就是最後的答案(num爲狀態總數)。

初始條件:f[1][i]=1 (1<=i<=a[1].num).

相關文章
相關標籤/搜索