题意: 给你一个无根树,每个节点如果不是服务器,那么它当且仅当与一个服务器相邻。问你在这个无根树中,最少把多少个节点变为服务器,才能使方案合法。
分析:明显的树形dp,按照之前做紫书题目的经验,我们也是把节点分情况讨论,以便覆盖所有状态。
定义 dp (1,u)为: 当前u是服务器。 dp(0,u) 为u不是服务器,但它的父亲是服务器。dp(2,u)为 : u不是服务器,且它的父亲也不是服务器.
对于dp(1,u) 来说 当前是服务器,那么每个子节点v可以是服务器,也可以不是服务器,取min即可。
对于dp(0,u):它的父亲已经是服务器了,为了保持u只与一个子节点相邻,那么它的子节点一定不能是服务器。注意,子节点的状态是父亲不是服务器,自己也不是服务器。
所以dp(0,u)= sum(dp(2,v)) (v是u的子节点).
对于dp(2,u),必须u不是叶子结点,该项才合法。因为dp(2,u)父亲不是,自己不是,那么必须要有一个子节点才是。那么,该选哪个子节点成为服务器呢。
可以预见,选一个子节点成为服务器肯定比多选的要好,所以是dp(2,u)= sum - dp(2,v) + dp(1,v) 。其中sum是各个叶结点的dp(2,v)的和。等等!,这个sum其实就是dp(0,u)嘛,实际上我们先更新dp(1,u) 再更新dp(0,u),最后更新dp(2,u)不就好了?!
最后由于是无根树,我们随便找一个节点当成根就好,那就1号吧。最后答案是min(dp[1][1],dp[2][1])。为什么不考虑dp[0][1]呢,因为它并不合法,因为1是根,它根本就没有父亲嘛。
最后代码:
#include<bits/stdc++.h> using namespace std; const int maxn = 10005; const int INF=10006; vector<int> v[maxn]; int dp[3][maxn]; void dfs(int u,int fa){ dp[1][u]=1;dp[0][u]=0;dp[2][u]=INF; for(int i=0;i<v[u].size();i++){ if(v[u][i]==fa) continue; dfs(v[u][i],u); dp[1][u]+=min(dp[1][v[u][i]],dp[0][v[u][i]]); dp[0][u]+=dp[2][v[u][i]]; } for(int i=0;i<v[u].size();i++){ if(v[u][i]==fa) continue; dp[2][u]=min(dp[0][u]+dp[1][v[u][i]]-dp[2][v[u][i]],dp[2][u]); } return ; } int main(){ int n; while(scanf("%d",&n)==1){ for(int i=0;i<=n;i++){ v[i].clear(); } for(int i=0;i<n-1;i++){ int from,to; scanf("%d%d",&from,&to); v[from].push_back(to);v[to].push_back(from); } dfs(1,0); int op; cin>>op; cout<<min(dp[1][1],dp[2][1])<<endl; if(op==-1) break; } return 0;}