給一棵有根樹(根 1),邊有權。Q 次查詢,每次給 k 個關鍵點。需刪除一些邊,使根與所有關鍵點斷開,最小化刪除邊權和。Σk ≤ O(n)。
每次查詢只涉及少量關鍵點,若在整棵樹上 DP 會 O(nQ) 過慢。
構建虛樹:只包含關鍵點及其 LCA,邊權為原樹路徑上最小邊權。
虛樹邊數 = O(k),所有查詢總邊數 = O(Σk) = O(n)。
dfn(進入時間)、depth、倍增祖先 fa[u][i]、minEdge[u][i](向上 2^i 邊最小權)。dfn 排序。在虛樹上從根(1)遞歸:
f(u) = sum_{v 子節點} min(f(v), minEdge(u,v))
若 u 是關鍵點,則必須切斷與父邊(或子邊),即 f(u) = INF(葉子關鍵點返回 INF,父節點 min 時會選擇切邊權)。
實際代碼中用 is_key[u] 標記,DP 時若為關鍵點則返回 INF。
// 倍增預處理
void dfs(int u, int p) {
dfn[u] = ++timer; dep[u] = dep[p] + 1; fa[u][0] = p;
for (auto [v, w] : tree[u]) if (v != p) {
minE[v][0] = w;
dfs(v, u);
}
}
void build_lca() {
for (int j = 1; j < LG; ++j)
for (int i = 1; i <= n; ++i) {
fa[i][j] = fa[fa[i][j-1]][j-1];
minE[i][j] = min(minE[i][j-1], minE[fa[i][j-1]][j-1]);
}
}
// LCA + 路徑最小邊
int lca(int u, int v) { ... }
int get_min(int u, int v) { ... }
// 虛樹構建 + DP
long long dp(int u) {
long long sum = 0;
for (int v : vtree[u]) {
sum += min(dp(v), get_min(u, v));
}
return is_key[u] ? INF : sum;
}
這是「樹上查詢 + 關鍵點子集」的經典虛樹模板,SDOI 2011 此題為虛樹技術的標志性應用。