《資訊保安工程技術應用》課程設計報告
阿新 • • 發佈:2022-01-06
以下主要內容來自oi-wiki
樹上任意兩節點之間最長的簡單路徑(路徑上的頂點都不相同的路徑)即為樹的「直徑」。
一棵樹可以有多條直徑,他們的長度相等。
可以用兩次DFS或者樹形DP的方法在O(n)時間求出樹的直徑。
兩次DFS
首先從任意節點\(y\)開始進行第一次DFS,到達距離其最遠的節點,記為\(z\),然後再從\(z\)開始做第二次DFS,到達距離\(z\)最遠的節點,記為\(z^{\prime}\),則\(\delta(z,z^{\prime})\)即為樹的直徑。
顯然,如果第一次DFS到達的節點\(z\)是直徑的一端,那麼第二次DFS到達的節點\(z^{\prime}\)一定是直徑的一端。我們只需證明在任意情況下,\(z\)
證明:使用反證法。記出發節點\(y\)。設真實的直徑是\(\delta(s,t)\),而從\(y\)進行的第一次DFS到達的距離其最遠的節點\(z\)不為\(t\)或\(s\)。共分三種情況:
- 若\(y\)在\(\delta(s,t)\)上:
若\(\delta(y,z)>\delta(y,t)\),則最長路徑顯然為\(\delta(s,z)\),而不是\(\delta(s,t)\),故矛盾。
- 若\(y\)不在\(\delta(s,t)\)上,且\(\delta(y,z)\)與\(\delta(s,t)\)存在重合路徑。
若\(\delta(y,z)\)>\(\delta(y,t)\)
- 若\(y\)不在\(\delta(s,t)\)上,且\(\delta(y,z)\)與\(\delta(s,t)\)不存在重合路徑。
此時也容易得出\(\delta(s,t)\)不是最長路徑。
注意,若存在負權邊,則上述證明不成立,即有負權邊的情況下不能用DFS方法
參考程式碼
const int N = 10000 + 10; int n, c, d[N]; vector<int> E[N]; void dfs(int u, int fa) { for (int v : E[u]) { if (v == fa) continue; d[v] = d[u] + 1; if (d[v] > d[c]) c = v; dfs(v, u); } } int main() { scanf("%d", &n); for (int i = 1; i < n; i++) { int u, v; scanf("%d %d", &u, &v); E[u].push_back(v), E[v].push_back(u); } dfs(1, 0); d[c] = 0, dfs(c, 0); printf("%d\n", d[c]); return 0; }
樹形DP
我們記錄當1為樹根時,每個節點作為子樹向下所能延伸的最遠距離\(d_1\),和此遠距離\(d_2\),那麼直徑就是\(d_1+d_2\)的最大值。
樹形DP可以在存在負權邊的情況下求解出樹的直徑。
來張圖加深下理解。
參考程式碼
const int N = 10000 + 10;
int n, d = 0;
int d1[N], d2[N];//d1存最長,d2存次長
vector<int> E[N];
void dfs(int u, int fa) {
d1[u] = d2[u] = 0;
for (int v : E[u]) {
if (v == fa) continue;
dfs(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() {
scanf("%d", &n);
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d %d", &u, &v);
E[u].push_back(v), E[v].push_back(u);
}
dfs(1, 0);
printf("%d\n", d);
return 0;
}
性質
若樹上所有邊邊權均為正,則樹的所有直徑中點重合
反證法易證。