1. 程式人生 > 其它 >《資訊保安工程技術應用》課程設計報告

《資訊保安工程技術應用》課程設計報告

以下主要內容來自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;
}

性質

若樹上所有邊邊權均為正,則樹的所有直徑中點重合
反證法易證。