输入样例:
10
0 1
0 2
0 4
0 6
0 7
1 3
2 5
4 8
6 9
输出样例:
0
1
2
3
4
5
6
8
9
这道题是要求出所有位于树的直径上的点,树的直径求法很简单,只需要一次dp,令f1[i],f2[i]分别表示以i为根节点的字数中到达i节点最远,次远的边长,然后树的直径就是最远和次远边长的和的最大值,那我们能不能认为一次dp后最远和次元边长的和等于直径的点就是直径上的点,而小于直径上的点就不是直径上的点呢?这个地方需要好好理解一下,其实前半句话是对的,而后半句话不对,就是说一次dp后最远和次元边长的和等于直径的点就是直径上的点,但是小于直径的点也可能是直径上的点,为什么会这样呢?其实我们在dp过程中是从上向下进行的,我们每个点都无法向根节点方向搜索,所以无法得到真正的最大边和次大边,那有同学会问了,那为什么树的直径还是最远和次远边长的和的最大值呢?其实如果根节点是位于直径上的,那么显然答案是正确的,如果第一个根节点不位于直径上,他在向下遍历的过程中一定会遇到第一个在直径上的点,这个时候得到的答案也就随之正确了。所以说我们还需要求出每个节点向上搜索得到的最大长度,然后在向上搜索得到的最大长度和向下搜索得到的最大长度和次大长度三者之间取两个较大的数加和即可,若和等于直径,则该点位于直径上,否则不位于直径上,关于如何向上搜索和向下搜索,我在这就不细说了,不懂的小伙伴可以看下我之前的博客,下面附上博客地址:常见树形dp递归更新方法_AC__dream的博客-CSDN博客
下面是代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=1e6+10;
int h[N],ne[N],e[N],idx,f1[N],f2[N],f3[N];
void add(int x,int y)
{
e[idx]=y;
ne[idx]=h[x];
h[x]=idx++;
}
void dp1(int x,int father)//求以x节点为根的子树中最长和次长边的长度
{
for(int i=h[x];i!=-1;i=ne[i])
{
int j=e[i];
if(j==father) continue;
dp1(j,x);
if(f1[x]<f1[j]+1)
{
f2[x]=f1[x];
f1[x]=f1[j]+1;
}
else if(f2[x]<f1[j]+1)
f2[x]=f1[j]+1;
}
}
void dp2(int x,int father)//求不经过以x为根的子树所构成的最长边的长度
{
for(int i=h[x];i!=-1;i=ne[i])
{
int j=e[i];
if(j==father) continue;
if(f1[j]+1==f1[x])//如果该边就是x节点向下搜索得到的最长边,则用x节点向下搜索得到的次长边来进行更新
f3[j]=max(f3[x],f2[x])+1;
else
f3[j]=max(f3[x],f1[x])+1;
dp2(j,x);
}
}
int main()
{
int n;
cin>>n;
memset(h,-1,sizeof h);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
u++;v++;
add(u,v);add(v,u);
}
dp1(1,0);
dp2(1,0);
int mx=0;
for(int i=1;i<=n;i++)//先求出树的直径
mx=max(mx,f1[i]+f2[i]);
for(int i=1;i<=n;i++)
if(mx==f1[i]+max(f2[i],f3[i]))
printf("%d\n",i-1);
return 0;
}