SDOI2006 保安站岗
一道很好的树型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; }
当你意识到,每个上一秒都成为永恒。