本题是点的最小覆盖集问题,是一个树形dp问题。
对于某一个节点来说他能通信来自于他父亲以及他儿子节点以及他自身。那么dp[n][3]表示某个节点三这种情况下的最小值。
0:靠父亲。
1:靠自己。
2:靠儿子。
那么对于靠自己和靠父亲来说很简单的能写出状态转移方程:
dp[i][0] = (儿子节点的和)min(dp[u][1],dp[u][2]);
dp[i][1] = (儿子节点的和)min(dp[u][0],dp[u][1],dp[u][2]);
对于最后靠儿子的情况,只要有一个儿子有通信站即可,但究竟是哪一个被点亮呢?那么讨论对儿子来说父亲靠不住就只有两种情况dp[u][1]和dp[u][2]。那么如果有一个儿子节点dp[u][1]<=dp[u][2]的话就不需要付出额外的代价了。如果全部都dp[u][1]>dp[u][2]的话就得选择一个儿子节点将他变成自己建立通信站的情况,但这样必然会付出一些代价,所以我们选取dp[u][1]-dp[u][2]最小的那一个,这样所付出的代价最小。
#include <bits/stdc++.h> using namespace std; #define int long long const int maxn = 10000+10; int n; vector<int> son[maxn]; int dp[maxn][3]; void dfs(int x, int fa) { int len = son[x].size(); int add = INT_MAX; bool flag = false; dp[x][1] = 1; for (int i=0;i<len;i++) { int next = son[x][i]; if (next == fa) continue; dfs(next, x); dp[x][0] += min(dp[next][1], dp[next][2]); dp[x][1] += min(dp[next][0],min(dp[next][1], dp[next][2])); if (dp[next][1]>dp[next][2]) { add = min(dp[next][1]-dp[next][2],add); } else { flag = true; } } if (flag) { dp[x][2] = dp[x][0]; } else { dp[x][2] = dp[x][0]+add; } } signed main() { cin>>n; if (n==1) { cout<<1<<endl; return 0; } int x, y; for (int i=1;i<n;i++) { cin>>x>>y; son[x].push_back(y); son[y].push_back(x); } dfs(1, 0); cout<<min(dp[1][1], dp[1][2]); return 0; }