圖論專題-學習筆記:樹的直徑
1. 前言
樹的直徑是樹的一個小板塊,但是有著重要的應用。
前置知識:樹的基礎知識。
2. 詳解
例題:SP1437 PT07Z - Longest path in a tree
2.1 定義
樹的直徑:一棵樹上最長的路徑叫做樹的直徑。
比如下面這棵樹,帶有邊權 1 的路徑就是樹的直徑。
需要注意的是,這棵樹的直徑不止一條,但是一般情況下我們只取其中一條叫做這棵樹的直徑。
2.2 求法
那麼怎麼求樹的直徑呢?
這裡有兩種求法:DFS 與樹形 DP。
2.2.1 DFS 求解
該演算法的大致步驟如下:
- 首先隨便取一個點,做一遍 DFS,求出這個點能夠到達的最遠點。
- 然後從這個點再次 DFS,求出這個點能夠到達的最遠點。
- 求出來的兩個點之間的路徑就是樹的直徑。
步驟簡明易懂,那麼這個演算法為什麼是正確的呢?
下面證明假設邊權大於 0。
採用反證法:
假設圖中還存在一條比我們求出來的路徑更長的路徑,那麼這條路徑就是樹的直徑。
設我們求出的路徑為 \(AB\),真正的直徑為 \(CD\)。
分為兩種情況:
- 直徑與我們求出的路徑相交或者部分重合。
那麼根據上述演算法,我們在第一次找到點 \(A\) 的時候必有 \(AE>CE\)。
同理,有 \(AE+EF+FB>AE+EF+FD\)
考慮將 \(AE>CE\) 帶入上述不等式,有 \(AE+EF+FB>CE+EF+FD\),這與 \(CD\) 是直徑不符,所以上述情況不成立。
- 直徑與我們求出的路徑不重合。
則 \(CF+FD>CF+EF+EB\),因此 \(FD>EF+EB\),則有 \(FD>EB\)。
然而根據我們的演算法步驟,\(EB>EF+FD\),則有 \(EB>FD\)。
出現了矛盾,因此原假設錯誤。
綜上所述,\(AB\) 必須是直徑。
那麼為什麼說這個證明必須有邊權大於 0 呢?
這是因為如果邊權小於 0,那麼上述所有證明的 \(a>b+c\rightarrow a>c\)
同時這也揭示了 DFS 求樹的直徑必須有所有邊權大於 0。
DFS 的優點:可以記錄樹的直徑都有哪些點,也就是可以完整記錄路徑。
DFS 的缺點:不能處理有負邊權的樹。
2.2.2 樹形 DP 求解
這個需要有一定樹形 DP 基礎,當然沒有基礎也沒有問題。
考慮設 \(f1_i,f2_i\) 分別表示第 \(i\) 個節點到葉子節點路徑的最大值與次大值。
那麼設計狀態轉移方程:
設當前的點為 \(u\),當前列舉的兒子節點為 \(v\),邊權為 \(val\),那麼有如下方程:
- 如果 \(f1_v+val>f1_u\),那麼 \(f2_u\leftarrow f1_u,f1_u\leftarrow f1_v+val\)。
- 否則,\(f2_u=\max\{f2_u,f1_v+val\}\)。
轉移方程還是簡明易懂的吧qwq
最後的答案就是 \(\max\{f1_i+f2_i|i \in [1,n]\}\)。
那麼這個做法很明顯,因為是 DP 思想,可以處理具有負邊權的樹。
樹形 DP 的優點:可以處理具有負邊權的樹。
樹形 DP 的缺點:不能記錄路徑。
2.3 程式碼
兩種做法的程式碼如下(Solve_dfs
是 DFS 做法,Solve_DP
是樹形 DP 做法):
/*
========= Plozia =========
Author:Plozia
Problem:SP1437 PT07Z - Longest path in a tree
Date:2021/4/27
Another:樹的直徑模板題
========= Plozia =========
*/
#include <bits/stdc++.h>
typedef long long LL;
const int MAXN = 1e4 + 10;
int n, Head[MAXN], cnt_Edge = 1, f[MAXN], ans, root, f1[MAXN], f2[MAXN];
struct node { int Next, to, val; } Edge[MAXN << 1];
int read()
{
int sum = 0, fh = 1; char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
return sum * fh;
}
int Max(int fir, int sec) { return (fir > sec) ? fir : sec; }
void add_Edge(int x, int y, int z) { ++cnt_Edge; Edge[cnt_Edge] = (node){ Head[x], y, z }; Head[x] = cnt_Edge; }
void dfs(int now, int father)
{
if (f[now] > ans) { ans = f[now]; root = now; }
for (int i = Head[now]; i; i = Edge[i].Next)
{
int u = Edge[i].to;
if (u == father) continue ;
f[u] = f[now] + Edge[i].val;
dfs(u, now);
}
}
void Solve_DFS()
{
root = 1;
f[root] = 0; dfs(root, 0); ans = 0;
f[root] = 0; dfs(root, 0);
printf("%d\n", ans);
}
void DP(int now, int father)
{
for (int i = Head[now]; i; i = Edge[i].Next)
{
int u = Edge[i].to;
if (u == father) continue ;
DP(u, now);
if (f1[u] + Edge[i].val > f1[now]) { f2[now] = f1[now]; f1[now] = f1[u] + Edge[i].val; }
else { f2[now] = Max(f2[now], f1[u] + Edge[i].val); }
}
}
void Solve_DP()
{
DP(1, 0);
for (int i = 1; i <= n; ++i) ans = Max(ans, f1[i] + f2[i]);
printf("%d\n", ans);
}
int main()
{
n = read();
for (int i = 1; i < n; ++i)
{
int u = read(), v = read();
add_Edge(u, v, 1); add_Edge(v, u, 1);
}
// Solve_DFS(); return 0;
Solve_DP(); return 0;
}
3. 總結
樹的直徑:樹上最長路徑。
求法:
- 兩遍 DFS 求解。
- 優點:可以記錄路徑。
- 缺點:不能處理負邊權。
- 樹形 DP 求解。
- 優點:可以處理負邊權。
- 缺點:不能處理路徑。