目录
定义
树上任意两点之间最长的简单路径即为树的直径;
引理:在树中,对于任意节点 u,存在节点 v 使得 d(u,v) 最大,且 v 是树的某个直径端点;
求解
方法一:两次DFS/BFS算法(更常用、更直观)
算法核心:
这是一个基于搜索的算法,其核心思想在于一个关键性质:从树中的任意一点出发,找到距离它最远的点,这个点一定是某条直径的一个端点。然后从这个端点出发,再次寻找距离它最远的点,这两个端点之间的路径就是树的直径。
模板:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+55;
int n,lr, // lr记录最长路径长度
pos, // pos记录最长路径的端点
dep[N]; // dep记录每个节点的深度(距离起始点的距离)
vector<int> g[N]; // 邻接表存储树结构
queue<int> q; // BFS使用的队列
// BFS函数,从x节点开始搜索,返回距离x最远的节点
int bfs(int x){
// 清空队列
while(!q.empty()) q.pop();
// 初始化dep数组为0
memset(dep,0,sizeof(dep));
// 从x节点开始BFS
q.push(x);
dep[x]=1; // 起始节点深度为1
while(!q.empty()){
int u=q.front(); // 取出队首节点
q.pop();
// 遍历u的所有邻居节点
for(auto v:g[u]){
if(!dep[v]){ // 如果v未被访问过
dep[v]=dep[u]+1; // 计算v的深度
// 更新最长路径信息
if(dep[v]>lr){
lr=dep[v]; // 记录最长深度
pos=v; // 记录对应节点
}
q.push(v); // 将v加入队列
}
}
}
return pos; // 返回距离x最远的节点
}
int main(){
// 优化输入输出
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
// 输入节点数量
cin>>n;
// 输入n-1条边,构建树结构
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
g[u].push_back(v); // 无向图,双向添加
g[v].push_back(u);
}
// 计算树的直径:
// 1. 从任意节点(1)开始BFS,找到最远节点pos
// 2. 从pos节点开始BFS,找到最远节点,此时lr-1就是直径长度
bfs(bfs(1));
// 输出直径长度(lr-1是因为初始节点深度从1开始计算)
cout<<lr-1;
return 0;
}
复杂度分析:
时间复杂度:O(n),进行了两次DFS,每次都遍历了所有节点。
空间复杂度:O(n),用于存储树结构和距离数组。
方法二:树形动态规划(处理更复杂的问题)
核心思想:
对于树中的每个节点,我们考虑经过该节点的最长路径。那么,整个树的直径就是所有节点计算出的“经过该节点的最长路径”中的最大值。
状态定义:
设 d1[u] 表示以 u 为根的子树中,从 u 出发能到达的最远距离(也称为u的“向下最长链”)。设 d2[u] 表示以 u 为根的子树中,从 u 出发能到达的次远距离(与最远路径没有公共边)。
状态转移与答案更新:
在DFS遍历节点u时,遍历其所有子节点v:
-
从子节点
v回来时,我们得到一个候选值length = d1[v] + w(u, v)。 -
用这个
length去更新d1[u]和d2[u]。-
如果
length > d1[u],则d2[u] = d1[u],d1[u] = length。 -
否则如果
length > d2[u],则d2[u] = length。
-
-
那么,经过节点
u的最长路径长度就是d1[u] + d2[u]。我们用这个值去更新全局的直径答案ans = max(ans, d1[u] + d2[u])。
模板:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int dp[N],dp2[N];
vector<int> g[N];
void dfs(int u,int fa){
//dp[u]=dp2[u]=-1e9;//如果有负边权就需要初始化,但是叶节点要初始化成0
//bool flag=false;//判断该节点是不是叶节点
for(auto v:g[u]){
if(v==fa) continue;
//flag=true;//如果可以继续向下搜索就不是叶节点
dfs(v,u);
if(dp[v]+1>dp[u]){//注意这里如果有边权要加上边权
dp2[u]=dp[u];//如果从这条路走的长度比dp[u]要长
dp[u]=dp[v]+1;
}
else if(dp[v]+1>dp2[u]){
dp2[u]=dp[v]+1;//如果从这条路走的长度比dp2[u]要长
}
}
//if(!flag) dp[u]=dp2[u]=0;//如果是叶节点就要初始化成0
return;
}
int main(){
int n,ans=0;//ans用于最后求max,所以如果有负边权就要初始化成-1e9
cin>>n;
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);//建图
}
dfs(1,0);//从任意点遍历
for(int i=1;i<=n;i++)//注意最后要遍历找答案
ans=max(ans,dp[i]+dp2[i]);//dp[i]+dp2[i]就是经过点i的最长的路
cout<<ans<<endl;
return 0;
}
复杂度分析:
时间复杂度:O(n),只需一次DFS。
空间复杂度:O(n)。
1489

被折叠的 条评论
为什么被折叠?



