经典问题《树的直径》与《树的中心》,详解。


树的直径

1、定义

树的直径: 树上最远的两个节点之间的距离。
连接这两点的路径被称为树的最长链


2、如何求出一棵树的直径?

将任意点看作根节点。

对于节点 x 来说:

  • 如果最长链在以节点 x 为根的子树中,并且经过节点 x,那么最长链的长度就是从节点 x 到最底端的两个最长路径距离之和。

容易想到,最长链一定在一个节点为根的子树中,并且经过该节点,所以我们只需要在所有节点下,找出每个点 x 到最底端的 最长距离 和 ‘次长距离’,维护两个距离和的最大值即可。
最终的最大值便是这棵树的直径。
(注意,这个 ‘次远距离’ 并不是真正的从 x 到最底端的次远,而是再用 x 的其他子节点来更新,所找到的最远距离。)

从根节点出发,我们可以 dfs 维护出每个点到最底端的最长距离,定义为 f[x],在这个过程中,维护两个最长距离不断更新最长链长度 ans

算法流程:
对于当前节点 x,定义 max1,max2 为该点到最底端的最长的两个距离,初始为0。遍历所有子节点:

  • 类似于记忆化搜索,子节点之前没走过,那么递归这个点到最后,返回的便是该点到最底端的最长距离 dist。
  • 如果 dist 大于维护的最长距离 max1,那么把次大值 max2 赋值为 max1max1 更新为 dist
    否则如果 dist 大于维护的次长距离 max2,那么把次大值 max2 更新为 dist
  • 最长链 ans = max(ans, max1+max2),将 f[x] 赋值为 max1
  • return 返回 f[x] 给上一层。
完整Code:
#include<bits/stdc++.h>
using namespace std;

const int N = 200010, mod = 1e9+7;
int T, n, m, k;
int a[N], ans;
vector<PII> e[N];
int f[N], ru[N];

int dfs(int x, int fa)
{
	int max1=0, max2=0;
	
	for(auto it:e[x])
	{
		int tx = it.fi, w = it.se;
		
		if(tx == fa) continue;
		int d = dfs(tx, x)+w;
		if(d > max1) max2 = max1, max1 = d;
		else if(d > max2) max2 = d;
	}
	ans = max(ans, max1+max2);
	
	f[x] = max1;
	return f[x];
}

signed main(){
	Ios;
	cin>>n;
	
	for(int i=1;i<n;i++)
	{
		int x, y, z;cin>>x>>y>>z;
		e[x].pb({y, z});
		e[y].pb({x, z});
	}
	
	dfs(1, 0);
	
	cout << ans;
	
	return 0;
}

3、如何输出这条最长链上所有点?

输出路径上所有点,便需要记录路径。这里所用到的方法和 最短路 记录路径方法类似:
先记录下找到最长链的根节点 pos,然后记录每个点到最底端的最长和次长路径上的下一个节点。
如此,从 pos 节点便可以还原出到最底端的两条路径,将两条路径拼接便是最长链。

完整Code:
#include<bits/stdc++.h>
using namespace std;

const int N = 200010, mod = 1e9+7;
int T, n, m, k;
int a[N], ans;
vector<PII> e[N];
int f[N], ru[N];
int ne[N][2], pos;

int dfs(int x, int fa)
{
	int max1=0, max2=0;
	
	for(auto it:e[x])
	{
		int tx = it.fi, w = it.se;
		
		if(tx == fa) continue;
		int d = dfs(tx, x)+w;
		
		if(d > max1) max2 = max1, max1 = d, ne[x][1]=ne[x][0], ne[x][0]=tx;
		else if(d > max2) max2 = d, ne[x][1]=tx;
	}
	if(max1 + max2 > ans){
		ans = max1+max2;
		pos = x;
	}
	
	f[x] = max1;
	return f[x];
}

signed main(){
	Ios;
	cin>>n;
	
	for(int i=1;i<n;i++)
	{
		int x, y, z;cin>>x>>y>>z;
		e[x].pb({y, z});
		e[y].pb({x, z});
	}
	
	dfs(1, 0);
	cout << ans << endl;
	
	int x = ne[pos][0];
	stack<int> stk; //存储根节点pos到最底端的最长路径,以保证输出的节点顺序
	while(x)
	{
		stk.push(x);
		x = ne[x][0];
	}
	
	while(stk.size()) cout<<stk.top()<<" ", stk.pop(); 
	cout << pos <<" ";
	
	x = ne[pos][1];
	while(x)
	{
		cout << x <<" ";
		x = ne[x][1];
	}
	
	
	return 0;
}

最后附一个关于树的直径的思维题:
https://www.bbsmax.com/A/obzbpLGj5E/



树的中心

1、定义

树的中心: 所有节点中,到树中其他节点的最远距离 最小的节点。


2、如何找出树的中心,并求出 该点到其他节点的最远距离?

首先把任意节点当作根节点。

对于一个节点来说,到其他节点的最远距离可以分成两种情况:

  1. 从该节点直接往下走,到底端的最远距离 up[x];
  2. 从该节点先往上走,到其他节点的最远距离 down[x]。

这两种情况取 max 便是到其他节点的最远距离。

对于第一种情况
很好求,直接往下递归维护最大值,根据子节点传回来的答案更新父节点。

int dfs_down(int x, int fa) //从上往下递归,在回溯的时候由下往上更新
{
	for(auto it:e[x])
	{
		int tx = it.fi, w = it.se;
		if(tx == fa) continue;
		
		int d = dfs_down(tx, x);
		if(d + w > d1[x]) d2[x] = d1[x], d1[x] = d+w, ne[x] = tx;
		else if(d + w > d2[x]) d2[x] = d+w;
	}
	return d1[x];
}

至于d1,d2,ne数组的作用,会在第二种情况中做解释。

对于第二种情况
需要由父节点的答案更新子节点。先贴出代码。

int dfs_up(int x, int fa) //从上往下递归,从上往下更新
{
	for(auto it:e[x]) //遍历节点x的所有字节点tx
	{
		int tx = it.fi, w = it.se;
		if(tx == fa) continue;
		
		if(ne[x] != tx) up[tx] = max(up[x], d1[x])+w;
		else up[tx] = max(up[x], d2[x])+w;
		
		dfs_up(tx, x);
	}
}

假设当前点为 x,其父节点为 fa。
对于 点x先往上走,到其他节点的最远距离 up[x] 需要由父节点fa的该状态 up[fa] 来更新:
fa先往上走,到其他节点的最远距离up[fa]fa往下走到底端,且不经过当前点x的最远距离 取max + 边长w 来更新 当前点x先往上走,到其他节点的最远距离up[x]

但是如何得到 fa往下走到底端,且不经过当前点x的最远距离 呢?

  • 我们在第一种情况的 dfs 中已经维护了 fa 往下走到底端的最远距离 down[x],如果点 x 不在这个路径中,那么第一种情况中所维护的 down[x] 就是满足的;
  • 但是如果点 x 在这个路径中,就需要找到 fa 往下走到底端的‘次远距离’。 (同样注意,这个 ‘次远距离’ 并不是真正的从 fa 到最底端的次远,而是再从 x 的其他兄弟节点中更新,所找到的最远距离。)

于是,就需要像上半部分求《树的直径》的递归一样,分别记录 最远距离d1[i]次短距离d2[i]。同时用 ne[fa] 记录
fa 到底端的最远距离是用哪个子节点更新过来的,用于判断子节点 x 是否在最远距离的路径中。

综上可以得到第二种情况的递归流程:
自上而下递归遍历所有点。对于节点 x,遍历其所有子节点 tx:

  • 如果 tx 不在节点 x 到底端的最长距离路径中,就用 到底端的最长距离d1[x]节点x的答案up[x] 取max + 边长w 来更新 up[tx]
  • 否则,用 次长距离d2[x]节点x的答案up[x] 取max + 边长w 来更新 up[x]
  • 递归到子节点 tx。

最后:
对于一个点x,向上走到其他点的最远距离up[x] 和 向下走到其他点的最远距离d1[i] 取max,便是该点到其他所有点的最远距离。
遍历所有点,取最远距离的最小值 便是 中心点到其他所有点最远距离的最小值。

for(int i=1;i<=n;i++)
	ans = min(ans, max(d1[i],up[i]));

完整Code:
#include<bits/stdc++.h>
using namespace std;

const int N = 200010, mod = 1e9+7;
int T, n, m, k;
int a[N];
vector<PII> e[N];
int d1[N], d2[N], ne[N], up[N];

//递归求出每个节点往下走,到其他节点的最远距离
int dfs_down(int x, int fa) //在回溯的时候自下而上更新
{
	for(auto it:e[x])
	{
		int tx = it.fi, w = it.se;
		if(tx == fa) continue;
		
		int d = dfs_down(tx, x);
		if(d + w > d1[x]) d2[x] = d1[x], d1[x] = d+w, ne[x] = tx;
		else if(d + w > d2[x]) d2[x] = d+w;
	}
	return d1[x];
}

//递归求出每个节点先往上走,到其他节点的最远距离
int dfs_up(int x, int fa) //在递归的时候自上而下更新
{
	for(auto it:e[x])
	{
		int tx = it.fi, w = it.se;
		if(tx == fa) continue;
		
		if(ne[x] != tx) up[tx] = max(up[tx], max(up[x], d1[x])+w);
		else up[tx] = max(up[tx], max(up[x], d2[x])+w);
		
		dfs_up(tx, x);
	}
}

signed main(){
	Ios;
	cin>>n;
	
	for(int i=1;i<n;i++)
	{
		int x,y,z;cin>>x>>y>>z;
		e[x].pb({y, z});
		e[y].pb({x, z});
	}
	
	dfs_down(1, 0);
	dfs_up(1, 0);
	
	int ans = 1e9;
	for(int i=1;i<=n;i++)
	{
		ans = min(ans, max(d1[i],up[i])); //向上走向下走两种情况取max就是点i到其他所有点的最远距离
	}
	cout << ans;
	
	return 0;
}

芜湖~,刺激!

如果哪里有问题欢迎留言讨论~

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值