题意:
有n个顶点,n - 1条边,没个顶点的度最多为3,根节点是1,他的度最多为2,不幸的是根节点被感染了,有n次事件会发生,Misha选择一个没有被感染过的点,删了他,删了他相当于删了他所有的边,或者什么都不做,这样之后先前被感染的点将会传染给与他相连的点,求他能够救的做多的边,被删除的边不能算作被救
思路:
很明显这是一棵树,自己想下,就可以想到每次删点肯定删有更多的儿子节点的点,但是这样一层一层不断的要找那个最大的很难实现,这时可以想想,树,能不能从尾部做手脚,尾部的值都是可以计算的,以尾部向上推移,树上dp的思想就可以体现出来了,对于每个点我肯定要知道他的子节点有多少个,这个不难,只要dfs跑一遍即可,算出了子节点的数量,我就可以去观察状态,刚开始只有1节点是感染的,他的子节点是不感染的,这样的情况最多能够救多少子节点,那在这个过程中,我也可以假设一个节点是是感染的,然后看看最多能够救多上个,有三种情况
如果是最后的节点是感染点的话,一个都救不了
一个点是感染点,他的子节点只有一个,那他能救的就是他的子节点数 - 1,因为他的直属子节点是要被删掉的
一个点是感染点,他的子节点有两个,那他就要比较哪个子节点当感染点的代价要小,dp[a] + cnt[b] - 1代表当a当感染点时,能够救的节点,dp[b] + cnt[a] - 1代表当b当感染点时,能够救的节点dp[x] = max(dp[a] + cnt[b] - 1, dp[b] + cnt[a] - 1)
#include <bits/stdc++.h>
#define endl '\n'
#define IOS ios::sync_with_stdio(false);
using namespace std;
typedef long long ll;
const int N = 3e5 + 10;
int T, n;
vector<int> v[N];
int cnt[N]; //记录每个节点的子节点数,注意这里也包含了他自身
int dp[N]; //dp[i]代表第i个点被感染时,能够就的最多的节点数
void Init()
{
for (int i = 1; i <= n; ++i)
{
v[i].clear();
}
}
void get_cnt(int x, int fa)
{
cnt[x] = 1;
for (int i = 0; i < v[x].size(); ++i)
{
int y = v[x][i];
if (y == fa)
continue;
get_cnt(y, x);
cnt[x] += cnt[y];
}
}
void get_dp(int x, int fa)
{
if (v[x].size() == 1 && x != 1) //说明这个就是最后的节点
{
dp[x] = 0;
return ;
}
else if (v[x].size() == 2 && x != 1) //说明这个是中间的只有一个子节点的点
{
for (int i = 0; i < v[x].size(); ++i)
{
int y = v[x][i];
if (y == fa)
continue;
dp[x] = cnt[y] - 1; //除了删除的那个点,其他的点都可以救
}
}
else //1节点和中间的有两个分支的点
{
int a = -1, b = -1;
for (int i = 0; i < v[x].size(); ++i)
{
int y = v[x][i];
if (y == fa)
continue;
if (a == -1)
a = y;
else
b = y;
get_dp(y, x);
}
dp[x] = max(dp[a] + cnt[b] - 1, dp[b] + cnt[a] - 1); //dp思想,还是模拟,中间的状态
}
}
int main()
{
//IOS; cin.tie(0), cout.tie(0);
cin >> T;
while (T--)
{
cin >> n;
Init();
for (int i = 1; i <= n - 1; ++i)
{
int x, y;
cin >> x >> y;
v[x].push_back(y);
v[y].push_back(x);
}
get_cnt(1, -1);
get_dp(1, -1);
cout << dp[1] << endl;
}
return 0;
}