樹形DP求樹的直徑

思路:

很是套路性的一個東西,記錄一下,防止遺忘
\(f[i]\)表示以\(i\)爲根,到其子樹的葉節點的最大距離。node

考慮如何用子節點更新父節點,
當前點到葉節點的最大距離=max{子節點到葉節點的距離+當前點到子節點的距離}。ios

\(u\)爲當前節點,\(v\)\(u\)的子節點,\(dis(u,v)\)是從\(u->v\)這條路徑上的距離
獲得轉移方程:
\[f[u]=max\{f[v]+dis(u,v)\}\]c++

如何維護以\(u\)爲根的子樹中的直徑呢
\(u\)爲根子樹的直徑=max{u到葉節點的最大距離+子節點到葉節點的最大距離+\(u\)到葉節點的距離}
而後咱們欽定一個節點爲根,好比1
獲得轉移方程:
\[ans=max\{f[u]+f[v]+dis(u,v)\}\]
\(ans\)即爲樹的直徑
須要注意的是,咱們要在更新\(f[u]\)以前更新\(ans\),由於從u通過v到葉節點的路徑是最長的路徑,這樣這條路徑會被更新兩次spa

這樣作必定會選出u到葉節點最長的兩條路徑
分類討論一下code

  • 更新\(f[u]\)的路徑是\(u\)到葉節點的全部路徑中最長的,次長的還未被選,那它會和次長(相等)的一同更新最大值
  • 更新\(f[u]\)的路徑是\(u\)到葉節點的全部路徑中次長的,最長的還未被選,那它會和最長的一同更新最大值
  • 更新\(f[u]\)路徑不是最長也不是次長,那麼\(f[u]\)就會被最長或次長的路徑更新,而後在轉化成上兩種狀況

代碼

void dfs(int u, int fa) {
    for (int i = head[u]; ~i; i = e[i].nx) {
        int v = e[i].v;
        if (v == fa) continue;
        dfs(v, u);
        ans = max(ans, f[u] + f[v] + e[i].w);
        f[u] = max(f[u], f[v] + e[i].w);
    }
}

練手題

#10155. 「一本通 5.2 例 3」數字轉換
邊權全爲1的樹的直徑ci

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int n, m, num, ans;

int head[N], f[N];

struct node {
    int nx, v;
} e[N];

inline int sum(int x) {
    int tmp = 1;
    for (int i = 2; i * i <= x; ++i) if (x % i == 0) {
        tmp += i;
        if (x / i != i) tmp += x / i; 
    }
    return tmp;
}

inline void add(int u, int v) {
    e[++num].nx = head[u], e[num].v = v, head[u] = num;
}

void dfs(int u, int fa) {
    for (int i = head[u]; ~i; i = e[i].nx) {
        int v = e[i].v;
        if (v == fa) continue;
        dfs(v, u);
        ans = max(ans, f[u] + f[v] + 1);
        f[u] = max(f[u], f[v] + 1);
    }
}

int main() {
    ios::sync_with_stdio(false);
    memset(head, -1, sizeof head);
    cin >> n;
    for (int i = 1; i <= n; ++i) if (sum(i) < i) 
        add(sum(i), i), add(i, sum(i));
    dfs(1, 0);
    cout << ans;
}
相關文章
相關標籤/搜索