目录:
树的直径
1、定义
树的直径: 树上最远的两个节点之间的距离。
连接这两点的路径被称为树的最长链。
2、如何求出一棵树的直径?
将任意点看作根节点。
对于节点 x 来说:
- 如果最长链在以节点 x 为根的子树中,并且经过节点 x,那么最长链的长度就是从节点 x 到最底端的两个最长路径距离之和。
容易想到,最长链一定在一个节点为根的子树中,并且经过该节点,所以我们只需要在所有节点下,找出每个点 x 到最底端的 最长距离 和 ‘次长距离’,维护两个距离和的最大值即可。
最终的最大值便是这棵树的直径。
(注意,这个 ‘次远距离’ 并不是真正的从 x 到最底端的次远,而是再用 x 的其他子节点来更新,所找到的最远距离。)
从根节点出发,我们可以 dfs 维护出每个点到最底端的最长距离,定义为 f[x]
,在这个过程中,维护两个最长距离不断更新最长链长度 ans
。
算法流程:
对于当前节点 x
,定义 max1,max2
为该点到最底端的最长的两个距离,初始为0。遍历所有子节点:
- 类似于记忆化搜索,子节点之前没走过,那么递归这个点到最后,返回的便是该点到最底端的最长距离 dist。
- 如果
dist
大于维护的最长距离max1
,那么把次大值max2
赋值为max1
,max1
更新为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、如何找出树的中心,并求出 该点到其他节点的最远距离?
首先把任意节点当作根节点。
对于一个节点来说,到其他节点的最远距离可以分成两种情况:
- 从该节点直接往下走,到底端的最远距离 up[x];
- 从该节点先往上走,到其他节点的最远距离 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;
}
芜湖~,刺激!
如果哪里有问题欢迎留言讨论~