1. 程式人生 > >SDOI2006 保安站崗

SDOI2006 保安站崗

algo col 發現 con 最小 三種 include names new

傳送門

一道很好的樹型DP。

一開始我的狀態選擇是用dp[i][0]表示以i為根節點,不選擇i的最小花費,dp[i][1]表示以i為根節點,選擇i的最小花費。但是這樣我發現無法轉移,因為你不能保證選或者不選的正確性……

問題在於狀態設少了。一個點有三種狀況,一個是本身站有保安,一個是被自己的子節點控制,一個未控制(將來會被父親控制)。我們就用dp[i][0]表示未被控制,dp[i][1]表示被自己的兒子節點控制,dp[i][2]表示本身站有保安。

之後轉移就很好辦,dp[i][0] = ∑min(dp[v][1],dp[v][2])(v是i的子節點,因為我們只要滿足這個點的子節點全部被控制即可),dp[i][2] = ∑min(dp[v][0],dp[v][1],dp[v][2]),因為反正這個點被控制了,子節點就無所謂了,dp[i][1] = ∑min(dp[v][1],dp[v][2]),然後強制選擇至少一個dp[v][2](否則的話這個節點相當於沒被子節點控制),強制選擇的話,記錄一下兩者差值的最小值,如果沒有選擇的話,直接加一個最小值即可。最後dp[i][2]要加上這個節點本身的花費。

之後就可以愉快的DP啦,看一下代碼。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<set>
#include<queue>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar(‘\n‘)

using
namespace std; typedef long long ll; const int M = 10005; const int INF = 1000000009; int read() { int ans = 0,op = 1; char ch = getchar(); while(ch < 0 || ch > 9) { if(ch == -) op = -1; ch = getchar(); } while(ch >= 0 && ch <= 9) { ans *= 10; ans
+= ch - 0; ch = getchar(); } return ans * op; } struct edge { int next,to; }e[M<<1]; int head[M],ecnt,w[M],dp[M][3],n,m,x,y; void add(int x,int y) { e[++ecnt].to = y; e[ecnt].next = head[x]; head[x] = ecnt; } void dfs(int x,int fa) { bool flag = 0; int minn = INF; for(int i = head[x];i;i = e[i].next) { int v = e[i].to; if(v == fa) continue; dfs(v,x); dp[x][2] += min(dp[v][0],min(dp[v][1],dp[v][2])); dp[x][0] += min(dp[v][1],dp[v][2]); if(dp[v][1] > dp[v][2]) dp[x][1] += dp[v][2],flag = 1; else dp[x][1] += dp[v][1],minn = min(minn,dp[v][2] - dp[v][1]); } dp[x][2] += w[x]; if(!flag) dp[x][1] += minn; } int main() { n = read(); rep(i,1,n) { x = read(),w[x] = read(),m = read(); rep(i,1,m) y = read(),add(x,y),add(y,x); if(!m) dp[x][1] = INF; } dfs(1,0); printf("%d\n",min(dp[1][1],dp[1][2])); return 0; }

SDOI2006 保安站崗