样例输入:
4
2
1 2
4
1 2
2 3
2 4
7
1 2
1 5
2 3
2 4
5 6
5 7
15
1 2
2 3
3 4
4 5
4 6
3 7
2 8
1 9
9 10
9 11
10 12
10 13
11 14
11 15
样例输出:
0
2
2
10
题意:
多组样例,给定一个具有n个结点的二叉树,根节点被病毒感染了。
每秒会有以下事件发生:
我们可以砍掉一个还未被感染的结点,那么以这个结点为根的子树上的结点全部都获救了。
被感染的点会把其孩子结点全部感染
我们现在要求最多能够拯救的结点数量。
分析:由于题目中说明树是一棵二叉树,所以我们每次就是砍掉一个分支,另一个分支的子树的根节点被感染,那么我们很容易想到,正常情况下每过一秒就会有一个结点被感染且有一个结点被砍掉,那这种情况会持续到什么时候呢?就是当遍历到一个叶子结点时,这个叶子结点会被直接感染,同时我们把他的兄弟结点砍掉而导致整个过程结束,还有一种情况就是遍历到一个只有一个分支的情况,那么下一个过程,这个单分支结点会被直接感染,那么我们这个分支根节点被感染后我们直接砍去他唯一的孩子结点,那么整个过程也就结束了,这两种情况分别对应着下面的左图和右图:
如果理解了上面的分析相信大家就能看到,每经过一秒,就会有两个结点牺牲,直到遇到单分支结点或者叶子结点为止,如果遇到单分支结点,最后就是单分支的孩子结点被砍,而如果是叶子结点,那么最后就是叶子结点被感染,通过这样的分析,大家肯定能发现每经过一秒牺牲的结点所在的层次数就会+1,直到遇到上面两种情况为止,所以题目就转化为求出距离根节点最近的叶子结点或者是单分支结点,如果距离根节点的最近结点是叶子结点,那么牺牲的结点数就是叶子结点所在的层数*2-1,而如果距离根节点的最近结点是单分支结点,那么牺牲的结点数就是单分支结点所在层数*2,答案就是总结点个数减去牺牲的结点个数即可。
注意根节点由于没有父节点,所以他的出边要比正常结点少1,所以需要特殊判断一下。
下面是代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
const int N=1e6+10;
int n,ans;
vector<int>p[N];
void dfs(int x,int fa,int d)
{
if(p[x].size()==2&&x!=1)
{
ans=max(ans,n-2*d);
return ;
}
else if(p[x].size()==1)
{
if(x!=1)
ans=max(ans,n-(2*d-1));
else ans=n-2;
return ;
}
for(int i=0;i<p[x].size();i++)
{
int j=p[x][i];
if(j==fa) continue;
dfs(j,x,d+1);
}
}
int main()
{
int T;
cin>>T;
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
p[i].clear();
ans=0;
int u,v;
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
p[u].push_back(v);
p[v].push_back(u);
}
dfs(1,0,1);
printf("%d\n",ans);
}
return 0;
}