题目传送门:Codeforces-1689 C: Infected Tree


有一棵树,它的根节点( 1 1 1 号结点)被感染了。之后每一秒都会扩散到与感染结点相邻的结点。然而在扩散前,可以删除一个与感染结点相邻的结点(去掉所有的边,包括与孩子结点的边),问最多能保存多少结点。
首先贪心是不行的,因为这个树是分层的,如果有两个子树节点数量一样,还要递归地向下比较,会很麻烦。
那么我们考虑自下而上的
d
p
dp
dp。设
d
p
i
dp_i
dpi 为以
i
i
i 为根节点(被感染)子树,做题目要求的操作,所能存下的最大数量结点,
c
h
i
ch_i
chi 为以
i
i
i 为根节点子树的结点总量。那么
j
j
j 有两个子树时,其父结点
j
j
j 的转移方程为:
d
p
j
=
max
(
d
p
j
,
d
p
i
+
c
h
j
−
c
h
i
−
2
)
dp_j = \max(dp_j, dp_i + ch_j - ch_i - 2)
dpj=max(dpj,dpi+chj−chi−2)
即,
i
i
i 结点被感染,另一个子树被保留,同时失去
j
j
j 与另一个子树的根节点。
如果是一条分支,那么我们直接剪掉就可以了。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 7;
vector<int> G[maxn];
int dp[maxn], ch[maxn];
void dfs(int x, int fa) {
ch[x] = 1;
for(int i=0; i<G[x].size(); ++i) {
int v = G[x][i]; if(v == fa) continue;
dfs(v, x); ch[x] += ch[v];
}
for(int i=0; i<G[x].size(); ++i) {
int v = G[x][i]; if(v == fa) continue;
dp[x] = max(dp[x], dp[v] + ch[x] - ch[v] - 2);
}
if((fa == -1 && G[x].size() == 1) || (fa != -1 && G[x].size() == 2))
dp[x] = max(dp[x], ch[x] - 2);
}
int main() {
int T, n, u, v;
cin >> T;
while(T--) {
cin >> n;
memset(dp , 0, sizeof(int) * (n + 2));
memset(ch, 0, sizeof(int) * (n + 2));
for(int i=1; i<=n; ++i) G[i].clear();
for(int i=1; i<n; ++i)
cin >> u >> v, G[u].push_back(v), G[v].push_back(u);
dfs(1, -1);
cout << dp[1] << endl;
}
return 0;
}