【树的直径】学习笔记

树的直径

定义

在一棵树中,若两点 \(P,Q\) 构成的路径长度最长,则称 \(P,Q\) 构成的路径长度为这棵树的直径(Diameter)

为方便,后面记 \(D(l,r)\) 表示 \(l,r\) 两点之间构成的路径是直径,长度为 \(dis(l,r)\)

求法

(1)两次 DFS

我们可以采用两次 DFS 的方法求一棵树的直径。

具体的,我们可以从根节点出发,先做一次 DFS,得到距离根节点最远的点 \(P\);再从点 \(P\) 出发做一次 DFS,得到距离点 \(P\) 最远的点 \(Q\)。此时 \(P,Q\) 两点在树上构成的路径即为这棵树的直径。

具体证明可以参照 OI-wiki

void dfs(int x, int fa) {
	for(int y : g[x]) {
		if(y == fa) continue;
		dis[y] = dis[x] + 1;
		dfs(y, x);
	}
}

void solve() {
	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);
	int u = 0, ans = 0;
	for(int i = 1; i <= n; i++) {
		if(u == 0 || dis[i] > dis[u]) u = i;
	}
	memset(dis, 0, sizeof dis);
	dfs(u, 0);
	for(int i = 1; i <= n; i++) {
		ans = max(ans, dis[i]);
	}
	cout << ans << endl;
}

(2)树形 DP

我们还可以采用树形 DP 的方法求一棵树的直径。

\(dp_{u}\) 表示在以 \(u\) 为根节点的子树中,从 \(u\) 出发的最长路径长度。根据定义可得状态转移方程:\(dp_u=\max\{dp_u,dp_v+w(u,v)\}\)

由于 \(u\) 可能不是直径的某个端点,所以我们需要考虑直径经过 \(u\) 的情况。那么我们可以令 \(u\) 为直径的中转站,那么直径 \(d\) 的求法就是,在 \(dp_u\) 利用 \(dp_v\) 更新前,\(d=\max\{d,dp_u+dp_v+w(u,v)\}\)

(代码实现先咕咕咕)

两种求法的对比

求法 优点 缺点 时间复杂度
两次 DFS 方便记录路径 无法处理带有负权边 \(\mathcal O(n)\)
树形 DP 可以处理负权边 实现较复杂 \(\mathcal O(n)\)

性质

  1. 显然,一棵树有多条直径,每条直径的长度相等;
  2. 若不存在非正权边,则所有直径的中点必然重合。

证明:如图,设两条中点不重合的直径为 \((s,t)\)\((s',t')\),中点分别为 \(x\)\(x'\)。则根据中点可得:\((s,x)=(x,t)=(s',x')=(x',t')\),则有:\((s,t')=(s,x)+(x,x')+(x',t')>(s,t)\),与直径矛盾,因此假设不成立,所有直径的中点重合。

tree-diameter4

例题选讲

B4016 树的直径

模板题,参考上面部分。

P3304 [SDOI2013] 直径

第一个问题直接两次DFS即可。难点在于第二个问题。我们要如何求出所有直径都经过的边数?

假设我们求出的直径是 \(D(l,r)\),长度为 \(d\)。若存在另一条直径,则它一定会经过 \(D\)。假设这条直径经过点 \(i\in D\),且与 \(D\) 重叠部分为 \(sD(i,l)\),另一端点为 \(p\),则其一定满足:\(dis(p,i)=dis(r,i)\)

因此,我们可以遍历直径上的每个点,求出距它最远的点到它的距离:

  • 若这个距离等于它到右端点的距离,则该点右侧的所有边都不可能被所有直径经过,将答案区间的上界缩小至当前点;
  • 若当前点出发的最远距离等于它到左端点的距离,则这条直径就是最后一条直径,该点左侧的所有边都不可能被所有直径经过,将答案区间的下界增大至当前点,并直接退出遍历。

最后输出答案区间的上下界之间的边数即可。

P2195 HXY造公园

在操作二中,贪心地,每次连接这两个联通块的直径的中点一定是最优的。因此我们只需要在一开始的时候求出每个联通块的直径,对每个联通块记录 \(d_u\)(根节点为 \(u\))表示它的直径。对于每次操作一输出 \(d_{find(x)}\) 即可;对于每次操作二合并两个联通块,并且

\[d_{find(x)}\leftarrow\max\left\{{d_{find(x)},d_{find(y)},\left\lceil \dfrac{d_{find(x)}}{2}\right\rceil+\left\lceil\dfrac{d_{find(y)}}2\right\rceil+1}\right\} \]

即可。

P5895 [IOI 2013] dreaming 梦想

这题与上一题类似,只不过这题中的边权可能不为 \(1\)。还是同样,贪心地,连接两棵树的直径中点所得到的新树直径一定是最小的。因此我们可以预处理出每棵树的直径与半径,考虑答案的构成部分。我们发现,最优的连接方法是:把所有树都连接到一棵树上,这样彼此两棵树的路径边数不超过 \(2\) 条。直径的构成只可能属于如下三种:

  1. 原本所有树的直径中的最大值;
  2. 原本所有树的半径的最大值、次大值与 \(L\) 之和;
  3. 原本所有树的半径中次大值、第三大值与 \(2L\) 之和。

我们只需要在上面三种构成中取最大值即可。

posted @ 2026-07-26 15:45  EnjoySilence  阅读(13)  评论(0)    收藏  举报