一:最小支配集
考慮最小支配集,每個點有兩種狀態,即屬於支配集合或者不屬於支配集合,其中不屬於支配集合時此點還需要被覆蓋,被覆蓋也有兩種狀態,即被子節點覆蓋或者被父節點覆蓋.總結起來就是三種狀態,現對這三種狀態定義如下:
1):dp[i][0],表示點 i 屬於支配集合,並且以點 i 為根的子樹都被覆蓋了的情況下支配集中所包含最少點的個數.
2):dp[i][1],表示點 i 不屬於支配集合,且以 i 為根的子樹都被覆蓋,且 i 被其中不少於一個子節點覆蓋的情況下支配集所包含最少點的個數.
3):dp[i][2],表示點 i 不屬於支配集合,且以 i 為根的子樹都被覆蓋,且 i 沒被子節點覆蓋的情況下支配集中所包含最少點的個數.即 i 將被父節點覆蓋.
對於第一種狀態,dp[i][0]含義為點 i 屬於支配集合,那么依次取每個兒子節點三種狀態中的最小值,再把取得的最小值全部加起來再加 1,就是dp[i][0]的值了.即只要每個以 i 的兒子為根的子樹都被覆蓋,再加上當前點 i,所需要的最少的點的個數,DP轉移方程如下:
dp[i][0] = 1 + ∑(u 取 i 的子節點)min(dp[u][0], dp[u][1], dp[u][2])
對於第三種狀態,dp[i][2]含義為點 i 不屬於支配集合,且 i 被其父節點覆蓋.那么說明點 i 和點 i 的兒子節點都不屬於支配集合,所以點 i 的第三種狀態之和其兒子節點的第二種狀態有關,方程為:
dp[i][2] = ∑(u 取 i 的子節點)dp[u][1]
對於第二種狀態,略有些復雜.首先如果點 i 沒有子節點那么 dp[i][1]應該初始化為 INF;否則為了保證它的每個以 i 的兒子為根的子樹被覆蓋,那么要取每個兒子節點的前兩種狀態的最小值之和,因為此時點 i 不屬於支配集,不能支配其子節點,所以子節點必須已經被支配,與子節點的第三種狀態無關.如果當前所選狀態中每個兒子都沒被選擇進入支配集,即在每個兒子的前兩種狀態中,第一種狀態都不是所需點最小的,那么為了滿足第二種狀態的定義(因為點 i 的第二種狀態必須被其子節點覆蓋,即其子節點必須有一個屬於支配集,如果此時沒有,就必須強制使一個子節點的狀態為狀態一),需要重新選擇點 i 的一個兒子節點為第一種狀態,這時取花費最少的一個點,即取min(dp[u][0] - dp[u][1])的兒子節點 u,強制取其第一種狀態,其他的兒子節點取第二種狀態,DP轉移方程為:
if(i 沒有子節點) dp[i][1] = INF
else dp[i][1] = ∑(u 取 i 的子節點)min(dp[u][0], dp[u][1]) + inc
其中對於 inc 有:
if(上面式子中的 ∑(u 取 i 的子節點)min(dp[u][0], dp[u][1]) 中包含某個 dp[u][0], 即存在一個所選的最小值為狀態一的兒子節點) inc = 0
else inc = min(dp[u][0] - dp[u][1]) (其中 u 取點 i 的兒子節點)
代碼:
1 void DP(int u, int p) {// p 為 u 的父節點 2 dp[u][2] = 0; 3 dp[u][0] = 1; 4 bool s = false; 5 int sum = 0, inc = INF; 6 for(int k = head[u]; k != -1; k = edge[k].next) { 7 int to = edge[k].to; 8 if(to == p) continue; 9 DP(to, u); 10 dp[u][0] += min(dp[to][0], min(dp[to][1], dp[to][2])); 11 if(dp[to][0] <= dp[to][1]) { 12 sum += dp[to][0]; 13 s = true; 14 } 15 else { 16 sum += dp[to][1]; 17 inc = min(inc, dp[to][0] - dp[to][1]); 18 } 19 if(dp[to][1] != INF && dp[u][2] != INF) dp[u][2] += dp[to][1]; 20 else dp[u][2] = INF; 21 } 22 if(inc == INF && !s) dp[u][1] = INF; 23 else { 24 dp[u][1] = sum; 25 if(!s) dp[u][1] += inc; 26 } 27 }
二:最小點覆蓋
對於最小點覆蓋,每個點只有兩種狀態,即屬於點覆蓋或者不屬於點覆蓋:
1):dp[i][0]表示點 i 屬於點覆蓋,並且以點 i 為根的子樹中所連接的邊都被覆蓋的情況下點覆蓋集中所包含最少點的個數.
2):dp[i][1]表示點 i 不屬於點覆蓋,且以點 i 為根的子樹中所連接的邊都被覆蓋的情況下點覆蓋集中所包含最少點的個數.
對於第一種狀態dp[i][0],等於每個兒子節點的兩種狀態的最小值之和加 1,DP轉移方程如下:
dp[i][0] = 1 + ∑(u 取 i 的子節點)min(dp[u][0], dp[u][1])
對於第二種狀態dp[i][1],要求所有與 i 連接的邊都被覆蓋,但是點 i 不屬於點覆蓋,那么點 i 的所有子節點就必須屬於點覆蓋,即對於點 i 的第二種狀態與所有子節點的第一種狀態有關,在數值上等於所有子節點第一種狀態的和.DP轉移方程如下:
dp[i][1] = ∑(u 取 i 的子節點)dp[u][0]
代碼:
1 void DP(int u, int p) {// p 為 u 的父節點 2 dp[u][0] = 1; 3 dp[u][1] = 0; 4 for(int k = head[u]; k != -1; k = edge[k].next) { 5 int to = edge[k].to; 6 if(to == p) continue; 7 DP(to, u); 8 dp[u][0] += min(dp[to][0], dp[to][1]); 9 dp[u][1] += dp[to][0]; 10 } 11 }
三:最大獨立集
對於最大獨立集,每個點也只有兩種狀態,即屬於點 i 屬於獨立集或者不屬於獨立集兩種情況:
1):dp[i][0]表示點 i 屬於獨立集的情況下,最大獨立集中點的個數.
2):dp[i][1]表示點 i 不屬於獨立集的情況下.最大獨立集中點的個數.
對於第一種狀態dp[i][0],由於 i 點屬於獨立集,所以它的子節點都不能屬於獨立集,所以對於點 i 的第一種狀態,只和它的子節點的第二種狀態有關.等於其所有子節點的第二種狀態的值加 1,DP轉移方程如下:
dp[i][0] = 1 + ∑(u 取 i 的子節點) dp[u][1]
對於第二種狀態dp[i][1],由於點 i 不屬於獨立集,所以子節點可以屬於獨立解,也可以不屬於獨立集,所取得子節點狀態應該為數值較大的那個狀態,DP轉移方程:
dp[i][1] = ∑(u 取 i 的子節點)max(dp[u][0], dp[u][1])
代碼:
void DP(int u, int p) {// p 為 u 的父節點 dp[u][0] = 1; dp[u][1] = 0; for(int k = head[u]; k != -1; k = edge[k].next) { int to = edge[k].to; if(to == p) continue; DP(to, u); dp[u][0] += dp[to][1]; dp[u][1] += max(dp[to][0], dp[to][1]); } }
參考文獻《圖論及應用》,以及部分網上的資料.
