1. 程式人生 > >狀態壓縮DP 與 樹狀DP

狀態壓縮DP 與 樹狀DP

本文摘自依然部落格---http://blog.sina.com.cn/acmstart

狀態壓縮動態規劃

   動態規劃的狀態有時候比較難,不容易表示出來,需要用一些編碼技術,把狀態壓縮的用簡單的方式表示出來。典型方式:當需要表示一個集合有哪些元素時,往往利用2進位制用一個整數表示。

   *:一般有個資料 n<16 或者 n<32 這個很可能就是狀態DP的標誌,因為我們要用一個int的二進位制來表示這些狀態。要注意好這些資料規模的提示作用。

   *:確定了為狀態DP,那麼第一步就是預處理,求出每行所有可能的狀態了,cnt記錄總的狀態數,stk[]記錄所有的可能狀態。以炮兵陣地為例:

      int cnt, stk[MAX];

       void findStk(int n){    // 求出所有可能的狀態。

           for(int i = 0; i < (1<<n); i ++)
              if(ok(i)){                       //  判斷這種狀態可不可行。
                  stk[cnt] = i;
                  sum[cnt ++] = getSum(i);     //  計算這種狀態包含了幾個炮兵。
              }
       }

       bool ok(int x){          //  判斷狀態x是否符合,即是否會出現兩個大炮間隔小於2。
           if(x & (x<<1)) return false;
           if(x & (x<<2)) return false;
           return true;
       }

       int getSum(int x){       //  求出狀態x中安裝了多少門大炮,x的二進位制有幾個1。
           int num = 0;
           while(x > 0){
               if(x & 1) num ++;
               x >>= 1;
           }
           return num;
        }

    *:然後就是DP部分了,明確好狀態轉移方程。先特殊處理第1行,然後按狀態轉移方程求出剩下的值。

經典問題:TSP

    一個n個點的帶權的有向圖,求一條路徑,使得這條路經過每個點恰好一次,並且路徑上邊的權值和最小(或者最大)。或者求一條具有這樣性質的迴路,這是經典的TSP問題。
    n <= 16 (重要條件,狀態壓縮的標誌)

    如何表示一個點集:

由於只有16個點,所以我們用一個整數表示一個點集:
例如:
    5 = 0000000000000101;(2進製表示)
    它的第0位和第2位是1,就表示這個點集裡有2個點,分別是點0和點2。
    31 = 0000000000011111; (2進製表示)
    表示這個點集裡有5個點,分別是0,1,2,4,5;
所以一個整數i就表示了一個點集;整數i可以表示一個點集,也可以表示是第i個點。


    狀態表示:

dp[i][j]表示經過點集i中的點恰好一次,不經過其它的點,並且以j點為終點的路徑,權值和的最小值,如果這個狀態不存在,就是無窮大。
    狀態轉移:
    單點集:狀態存在dp[i][j] = 0;否則無窮大。非單點集:
    1:狀態存在  dp[i][j] = min(dp[k][s] + w[s][j])
    k表示i集合中去掉了j點的集合,s遍歷集合k中的點並且dp[k][s]狀態存在,點s到點j有邊存在,w[s][j]表示邊的權值。
    2.:狀態不存在 dp[i][j]為無窮大。

    最後的結果是: min( dp[( 1<<n ) – 1][j] ) ( 0 <= j < n );

    技巧:利用2進位制,使得一個整數表示一個點集,這樣集合的操作可以用位運算來實現。例如從集合i中去掉點j:
    k = i & (~( 1<<j)) 或者 k = i - (1<<j)

樹型動態規劃

    樹本身就是一個遞迴的結構,所以在樹上進行動態規劃或者遞推是最合適不過的事情。
    必要條件:子樹之間不可以相互干擾,如果本來是相互干擾的,那麼我們必須新增變數使得他們不相互干擾。


Party at Hali-Bula

    題目大意:n個人形成一個關係樹,每個節點代表一個人,節點的根表示這個人的唯一的直接上司,只有根沒有上司。要求選取一部分人出來,使得每2個人之間不能有直接的上下級的關係,求最多能選多少個人出來,並且求出獲得最大人數的選人方案是否唯一。


    這是一個經典的樹型動態規劃,人之間的關係形成樹型結構,簡單的染色統計是不正確的
    DP部分:用dp[i][0]表示不選擇i點時,i點及其子樹能選出的最多人數,dp[i][1]表示選擇i點時,i點及其子樹的最多人數。

    狀態轉移方程:
    對於葉子節點: dp[k][0] = 0, dp[k][1] = 1
    對於非葉子節點i: dp[i][0] = ∑max(dp[j][0], dp[j][1]) (j是i的兒子)
                      dp[i][1] = 1 + ∑dp[j][0] (j是i的兒子) 
    最多人數即為:max(dp[0][0], dp[0][1])

    如何判斷最優解是否唯一?

    新加一個狀態dup[i][j],表示相應的dp[i][j]是否是唯一方案。
    對於葉子結點:dup[k][0] = dup[k][1] = 1.

    對於非葉子結點:

    1:i的任一兒子j,若(dp[j][0] > dp[j][1] 且 dup[j][0] == 0) 或 (dp[j][0] < dp[j][1] 且 dup[j][1] == 0) 或 (dp[j][0] == dp[j][1]),則dup[i][0] = 0

    2:i的任一兒子j有dup[j][0] = 0, 則dup[i][1] = 0

Strategic game

    題目大意:一城堡的所有的道路形成一個n個節點的樹,如果在一個節點上放上一個士兵,那麼和這個節點相連的邊就會被看守住,問把所有邊看守住最少需要放多少士兵。

    典型的樹型動態規劃:

    dproot[ i ]表示以i為根的子樹,在i上放置一個士兵,看守住整個子樹需要多少士兵。

    all[ i ]表示看守住整個以i為根的子樹需要多少士兵。

    狀態轉移方程:
    葉子節點:dproot[k] =1; all[k] = 0;
    非葉子節點: dproot[i] = 1 + ∑all[j](j是i的兒子);
                 all[i] = min( dproot[i], ∑dproot[j](j是i的兒子) );

  摘自依然部落格---http://blog.sina.com.cn/acmstart