这是一道经典的树形DP题目,特点是要优化状态 。
仔细思考为什么要将状态定义成紫书上说的那样,因为该题要求每台不是服务器的计算机恰好要和一台服务器计算机相邻, 然而如何由当前节点看出来这点呢? 如果只是看他的子节点显然是不够的,因为他的父节点也对这个条件起至关重要的影响 。 所以才要维护三种情况来将状态的转移弄清楚。
由于是自顶向下递归,如果u是服务器,那么子节点可以是服务器也可以不是 ; 如果u不是服务器,还不能判断其子节点有没有服务器,因为还要关心父节点,所以在u不是服务器的前提下又产生了如下两种情况 : 1.u的父亲是服务器 2.u的父亲不是
这样就很容易知道这三个状态对应的应该如何转移的了 。
刚接触树形DP ,总结一下 :
1.因为这是一棵树,用递归写,由于记录了子结果,所以每个结点实际上只访问了一次,因此对于像本题来说的题,是一棵无根树,也没有所谓方向,那么我们大可以用一个数组来记录每个结点是否访问过,如果访问过,那么他一定是父节点,或者他的值一定已经记录下了 。 这样就可以在一个结点内维护不同的状态了 。
2.对于这种题,我们要学会细化状态,使得状态可以完美覆盖所有情况 。 另外要注意边界条件,我的INF一开始开太大了,经过累加,会溢出。。。各种WA。。
#include<bits/stdc++.h> using namespace std; const int INF = 1000000;//开小一点。。够用就行 const int maxn = 10000 + 10; int n,a,b,d[maxn][5],vis[maxn]; vector<int> sons[maxn]; void dp(int u) { vis[u] = 1; d[u][0] = 1; d[u][1] = 0; d[u][2] = INF; int k = sons[u].size(); vector<int> cur; for(int i=0;i<k;i++) { int v = sons[u][i]; if(!vis[v]) { dp(v); cur.push_back(v); d[u][0] += min(d[v][0],d[v][1]); d[u][1] += d[v][2]; } } for(int i=0;i<cur.size();i++) { int v = cur[i]; d[u][2] = min(d[u][2],d[u][1] - d[v][2] + d[v][0]); } if(d[u][0]>INF) d[u][0] = INF;//防止溢出 if(d[u][1]>INF) d[u][1] = INF; if(d[u][2]>INF) d[u][2] = INF; return ; } int main() { while(~scanf("%d",&n)) { memset(vis,0,sizeof(vis)); for(int i=1;i<n;i++) { scanf("%d%d",&a,&b); sons[a].push_back(b); sons[b].push_back(a); } dp(1); int ans = min(d[1][0],d[1][2]); printf("%d\n",ans); scanf("%d",&a); if(a == -1) break; for(int i=1;i<=n;i++) sons[i].clear(); } return 0; }