树的直径=>学习笔记

news/2025/3/19 21:35:26/文章来源:https://www.cnblogs.com/panda-lyl/p/18781893

定义

树的直径是指 树上任意两节点之间最长的简单路径

显然一棵树可能不止一条直径,但它们长度相等。

求法

\(2\) 种解法求树的直径,分别是两次 dfs 和 dp。

两次 dfs

先从随机的一个点,假设是根节点,第一次 dfs 求出距离它最远的节点,假设这个节点为 \(u\),然后从 \(u\) 开始再次 dfs,求出距离点 \(u\) 最远的节点,\(2\) 个节点之间的距离就是树的直径。

例题:洛谷 B4016 树的直径

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>using namespace std;int read() {int x = 0, f = 1; char ch = getchar();while (!isdigit(ch)) {if (ch == '-') f = -1;ch = getchar();}while (isdigit(ch)) {x = (x << 3) + (x << 1) + (ch ^ 48);ch = getchar();}return x * f;
}const int maxn = 100005;
int n, c, d[maxn];
vector<int> g[maxn];
int leaf;void dfs(int u, int fa) {for (int i = 0; i < g[u].size(); i++) {int v = g[u][i];if (v == fa) continue;d[v] = d[u] + 1;// 更新最远距离节点 if (d[v] > d[leaf]) leaf = v;dfs(v, u);}
}int main() {n = read();for (int i = 1; i < n; i++) {int u = read(), v = read();g[u].push_back(v);g[v].push_back(u);}dfs(1, 0);// 从最远的节点出发 d[leaf] = 0, dfs(leaf, 0);printf("%d\n", d[leaf]);return 0;
}

如果它叫你输出树的直径上的节点怎么办呢?很简单,直接从 leaf 向上遍历就行。在第二次 dfs 时可以这样:

void dfs(int u, int fa, int time) {if (time == 2) f[u] = fa;for (int i = 0; i < g[u].size(); i++) {int v = g[u][i];if (v == fa) continue;d[v] = d[u] + 1;// 更新最远距离节点 if (d[v] > d[leaf]) leaf = v;dfs(v, u, time);}
}

time 是我们新增的参数,方便处理每个节点的父亲,这样就可以从 leaf 开始,一步一步地去访问树的直径的下一个节点。

在主函数增加这个代码:

for (int i = leaf; i; i = f[i])printf("%d ", i);

这就是输出,虽然会输出 (树的直径 + 1) 个节点,但其实就是 树的直径 条边。

如果还有边权,就直接把 d[v] = d[u] + 1 改成 d[v] = d[u] + edge,其中 edge 是边权。

总结

两次 dfs 的做法固然简便,但是,它也有个致命的缺点,就是 如果遇到负边权,那么直接 GG。

所以我们就要引出我们的 dp 做法啦。

dp 做法

(1) 开两个数组的 dp 做法

我们记 \(d_1\) 为每个节点作为子树的根向下所能延伸的最长路径长度,而 \(d_2\) 则是每个节点作为子树的根向下所能延伸的次长路径长度,但是 \(d_2\)\(d_1\) 的路径没有公共边。所以说树的直径就是 \(\max \left \{ d_2 + d_1\right \}\)

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>using namespace std;int read() {int x = 0, f = 1; char ch = getchar();while (!isdigit(ch)) {if (ch == '-') f = -1;ch = getchar();}while (isdigit(ch)) {x = (x << 3) + (x << 1) + (ch ^ 48);ch = getchar();}return x * f;
}const int maxn = 100005;
int n, c;
int d1[maxn], d2[maxn], d;
vector<int> g[maxn];void dp(int u, int fa) {d1[u] = d2[u] = 0;for (int i = 0; i < g[u].size(); i++) {int v = g[u][i];if (v == fa) continue;dp(v, u);int t = d1[v] + 1;if (t > d1[u]) {d2[u] = d1[u];d1[u] = t;}else if (t > d2[u])d2[u] = t;}d = max(d, d1[u] + d2[u]);
}int main() {n = read();for (int i = 1; i < n; i++) {int u = read(), v = read();g[u].push_back(v);g[v].push_back(u);}dp(1, 0);printf("%d\n", d);return 0;
}

(2) 开一个数组的 dp 做法

(1) 的做法,有个显而易见的缺点,就是空间复杂度大,数据一大就 GG。

所以我们采用 树形dp。设 \(dp_u\) 表示从 \(u\) 出发的最长路径。

其状态转移方程为 \(dp_u = \max (dp_u, dp_v + edge(u, v))\),其中 \(v\)\(u\) 的子节点,\(edge(u,v)\) 代表 \(u\)\(v\) 之间的边权。而树的直径,可以看作从一个节点出发,不同的两条路径加起来的和取最大值,所以我们有:\(d = \max(d, dp_u + dp_v + edge(u, v))\),其中 \(d\) 是树的直径,这个转移要放在 \(dp_u\) 的转移之前。因为如果放在后面,假设 \(dp_u = dp_v + edge(u, v)\),那么同一条便会被算两次,使得答案不正确。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>using namespace std;int read() {int x = 0, f = 1; char ch = getchar();while (!isdigit(ch)) {if (ch == '-') f = -1;ch = getchar();}while (isdigit(ch)) {x = (x << 3) + (x << 1) + (ch ^ 48);ch = getchar();}return x * f;
}const int maxn = 100005;
int n, c;
int d, dp[maxn];
vector<int> g[maxn];void dfs(int u, int fa) {for (int i = 0; i < g[u].size(); i++) {int v = g[u][i];if (v == fa) continue;dfs(v, u);d = max(d, dp[u] + dp[v] + 1);dp[u] = max(dp[u], dp[v] + 1);}
}int main() {n = read();for (int i = 1; i < n; i++) {int u = read(), v = read();g[u].push_back(v);g[v].push_back(u);}dfs(1, 0);printf("%d\n", d);return 0;
}

(注意: 这里边权假设都是 \(1\),具体要看各个题目的要求)

总结

(1) 的做法可能不太常见,但多学一点也没坏处。

虽然 dp 的做法已经解决了两次 dfs 遇到负边权会 GG 的问题,但是,dp 的做法也有个缺点。

这个做法它只求直径长度,并不知道经过了哪些节点。

小结

dp 的做法和两次 dfs 在有些题目中相辅相成,所以两种做法都要熟悉 (典型例子:洛谷 P3629 [APIO2010] 巡逻 后续会出题解,可在主页查看)

感谢 这里

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/901722.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用gradio实现实时语音识别

这里通过gradio来实现实时语音识别,通过上传一个文件,当点击提交后则在右侧输出其相应识别的文字。 实现代码比较简单,如下所示: import gradiodef convert(file_bytes):# 语音识别方法,其中file_bytes是采样率与对应numpy.ndarray实例return recognition_func(file_bytes[1])d…

英语四级跟练计划第一天

前言 今天给英语四级报了名,以我以往英语的水平来看,如果不早早开始复习,绝无通过的可能,走投无路的我只好去向deepseek求助,上一次320分的考生,这一次该付出何等的努力才可以考到425分,如此为我制定了90天的四级复习计划。我相信有很多人和我一样,都被英语四级通过给困…

202107191556 - 层次分析法计算流程

构造矩阵 一致性检验计算最大特征值计算一致性指标CI CI = ( λ - n ) / ( n - 1 )随机一致性指标RI取值计算一致性比率CR计算特征向量列向量归一化 求行和后归一化根据特征向量求最大特征值计算过程

crontab 命令

crontab -l 查看所有任务 crontab -e 编辑任务* * * * * /www/server/php/82/bin/php /www/wwwroot/default/1.php >> /www/wwwroot/default/cron_log.log 2>&1cd /www/wwwroot/www.pk777slots.com/ && php think test在那个目录运行 cd /www/wwwroot/www…

python实验一 20241202王凯

课程:《Python程序设计》 班级: 2412 姓名: 王凯 学号:20241202 实验教师:王志强 实验日期:2025年3月12日 必修/选修: 公选课 1.实验内容 (1)提前了解了一下python,了解它的地位排名,并熟悉Python开发环境;(2)练习Python运行、调试技能;(编写书中的程序,并进行…

2021-PTA模拟题-L2-3 这是二叉搜索树吗?(递归)

递归+二叉搜索树的性质做题历程:拿到手就开始建树,结点太多了所以不能数组建树,就结构体建树,建完发现还得建镜像,就懒得干了,去找大佬们的题解,发现大家都是直接通过二叉搜索树的性质来递归左右子树,一气呵成......还是做题做少了,照葫芦画瓢才过的。。。AcCode: #inc…

2021-PTA总决赛-L2-3 这是二叉搜索树吗?(递归)

递归+二叉搜索树的性质做题历程:拿到手就开始建树,结点太多了所以不能数组建树,就结构体建树,建完发现还得建镜像,就懒得干了,去找大佬们的题解,发现大家都是直接通过二叉搜索树的性质来递归左右子树,一气呵成......还是做题做少了,照葫芦画瓢才过的。。。AcCode: #inc…

Django数据库迁移命令

Django数据库迁移命令 迁移命令 migrate python manage.py migrate # 全部项目 python manage.py migrate AppName # 一个项目 执行迁移命令必须确保当前在项目目录下(执行ls命令能看到 manage.py文件), 然后使用 python manage.py migrate 即可。 如果IDE为pycharm也可以…

记录一个因为电平转换的问题

调试电赛三子棋的棋盘时,发现了一个很让人抓狂的问题(因为我旧的原理图是霍尔全部的引脚全部引了出来,单片机用18个引脚去直接读取),但是最近因为一些事情,需要重新复刻 这个项目,于是乎我使用了并转串芯片,74hc165,单片机仅需三个引脚即可获取所有端口的状态,但是调…

什么是供应链管理?四个流是什么?一文彻底搞懂!

你有没有遇到过这些问题?客户下单了,但生产还没跟上,结果交货延期,客户疯狂催单? 仓库库存爆满,但缺的产品偏偏一直补不上,导致库存积压+缺货断货? 供应商交期不稳定,原材料时多时少,生产计划一改再改,车间每天在救火?如果你的企业也有这些烦恼,那说明你的供应链管…