树的直径

以下主要内容来自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)\),则最长路线为\(\delta(s,z)\),而不是\(\delta(s,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;
}

性质

若树上所有边边权均为正,则树的所有直径中点重合
反证法易证。

posted @ 2022-02-13 15:23  何太狼  阅读(183)  评论(0编辑  收藏  举报