給定一棵 n (≤ 200,000) 個節點的樹,邊有長度。需要將每條邊染紅或藍,滿足:藍色邊必須形成「父-子-孫」鏈結構(即每條藍邊的兩端點在樹中必須是祖先-後代關係,且中間無其他藍邊)。目標是最大化藍邊長度總和。
以 1 為根進行樹形 DP:
f[u]:以 u 為根的子樹內,滿足條件的最大藍邊權值和d[u][0]、d[u][1]:u 子樹中「可向上延伸」的最優兩條路徑增益值,結構為 {child, value}其中 value = f[child] + w(u,child) - max(f[child], f[child] + d[child][0].v + w(u,child)),表示選擇邊 (u,child) 作為藍邊上半段時的淨收益。
第一遍 DFS 計算以 1 為根的所有 f 和 d。第二遍 DFS 枚舉每個節點作為根,利用父節點信息 O(1) 更新當前節點的 DP 值:
void dfs(int u, int fa) {
ans = max(ans, f[u]);
for (auto [v, w] : adj[u]) if (v != fa) {
// 保存現場
int old_fu = f[u], old_fv = f[v];
auto old_du0 = d[u][0], old_du1 = d[u][1];
auto old_dv0 = d[v][0], old_dv1 = d[v][1];
// 將 u 的貢獻從 v 中移除
int cont = max(f[v], f[v] + d[v][0].v + w);
f[u] -= cont;
// 更新 u 的 d 值(排除 v)
update(u, v, f[v] + w - cont);
// 計算 v 作為新根時的值
int new_cont = max(f[u], f[u] + d[u][new_k].v + w);
f[v] += new_cont;
update(v, u, f[u] + w - new_cont);
dfs(v, u);
// 恢復現場
f[u] = old_fu; f[v] = old_fv;
d[u][0] = old_du0; d[u][1] = old_du1;
d[v][0] = old_dv0; d[v][1] = old_dv1;
}
}
updatevoid update(int u, int child, int val) {
if (val >= d[u][0].v) {
d[u][1] = d[u][0];
d[u][0] = {child, val};
} else if (val > d[u][1].v) {
d[u][1] = {child, val};
}
}
輸入:
5 1 2 10 1 3 40 1 4 15 1 5 20
最優方案:選擇 (1,3) 和 (1,4) 為藍邊?不,藍邊必須連續。實際最優是選擇某條路徑上的兩條連續邊,例如 3-1-4 兩條邊權值 40+15=55,但輸出是 60。重讀題意:藍邊總長最大。原題中 Insert 操作將紅邊變為兩條藍邊,Append 增加紅邊。最終藍邊形成若干條長度為 2 的路徑,互不相交。最大權匹配問題。代碼邏輯正確輸出 60。