1. 程式人生 > 其它 >樹的直徑 (樹形dp)

樹的直徑 (樹形dp)

定義

樹的直徑: 樹中最長的簡單路徑.

狀態

由於本片講述的是樹形dp方法, 故需要設計狀態.
我們令dp[u][0]表示以 \(u\) 為根的子樹中最長的簡單路徑的長度, dp[u][0]\(u\) 為根的子樹中次長的簡單路徑的長度.
那麼, 最終答案 \(length = \mathop{max(dp[u][0] + dp[u][1])}\limits_{1 \leq u \leq n}\)

轉移

考慮如下情況:

顯然,
\(dp[0][0] = length(0 \to 3 \to 4)= 3\)
\(dp[0][1] = length(0 \to 2) = 2\)

思考: 如何從節點 \(0\) 去, 更新節點 \(u\)

?
由於 \(0\)\(u\) 的子節點, 將 \(u\) 加入到 \(0\) 中最長路徑中 (路徑變為u->0->3->4, 長度為 \(4\)), 易得
\(dp[u][0] = \mathop{max(dp[u][0], dp[v][0] + 1)}\limits_{v \ is\ u's\ son}\)

那麼 \(dp[u][1]\)呢?
首先明確一點: \(dp[u][0]\)\(dp[u][1]\) 不能同時由一個點更新, 什麼意思呢?如下圖, 藍色為最長路徑, 紅色為次長路徑.

可以發現, 如果由同一點更新, 那麼那個重複的點會被 重複經過 ,即 不再是簡單路徑 .
同樣易得: \(dp[u][1] = \mathop{max(dp[u][1], dp[v][0] + 1)}\limits_{v \ is\ u's\ son\ \&\ u\ not\ updated}\)

程式碼

明確了思路, 程式碼就很好寫了.
核心程式碼:

inline void dfs(int u, int fa) {//當前的節點點, 當前節點的祖先
    for(int i = head[u]; ~i; i = e[i].nxt) {//鏈式前向星
        int v = e[i].to;
        if(v == fa) continue;//防止向上走, 這是個常用的小技巧, 不需要記錄vis陣列
        dfs(v, u);//後序遍歷

        if(dp[u][0] < dp[v][0] + 1) { dp[u][1] = dp[u][0]; dp[u][0] = dp[v][0] + 1; }//dp[u][1]得到了除了v以外的最大值 + 1
        else if(dp[u][1] < dp[v][0] + 1) dp[u][1] = dp[v][0] + 1;//確保dp[u][0]沒有被更新
        res = max(res, dp[u][0] + dp[u][1]);//更新答案
    }
}