狀態壓縮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