給定一棵 n 個節點的樹(n ≤ 10,000),邊有權重。求節點對 (u, v) 使得 u 和 v 間距離 ≤ K 的數量。
選擇當前子樹的重心作為根(刪除重心後最大子樹節點數最小),遞歸處理:
1. 統計經過重心的合法點對
2. 遞歸處理各子樹(排除重心)
distdist,雙指針法計算兩數之和 ≤ K 的數對
int get_centroid(int u, int fa, int total_size) {
size[u] = 1; max_sub[u] = 0;
for (v : adj[u]) if (v != fa && !vis[v]) {
get_centroid(v, u, total_size);
size[u] += size[v];
max_sub[u] = max(max_sub[u], size[v]);
}
max_sub[u] = max(max_sub[u], total_size - size[u]);
if (max_sub[u] < max_sub[centroid]) centroid = u;
}
void get_dist(int u, int fa, int d, vector& dist) {
dist.push_back(d);
for (auto [v, w] : adj[u]) if (v != fa && !vis[v])
get_dist(v, u, d + w, dist);
}
int count_pairs(vector& dist, int K) {
sort(dist.begin(), dist.end());
int ans = 0, l = 0, r = dist.size() - 1;
while (l < r) {
if (dist[l] + dist[r] <= K) ans += r - l, ++l;
else --r;
}
return ans;
}
void solve(int u) {
get_centroid(u, -1, subtree_size(u));
int c = centroid; vis[c] = true;
vector all_dist; get_dist(c, -1, 0, all_dist);
ans += count_pairs(all_dist, K);
for (auto [v, w] : adj[c]) if (!vis[v]) {
vector sub_dist; get_dist(v, c, w, sub_dist);
ans -= count_pairs(sub_dist, K);
solve(v);
}
}
必須選擇重心分治,否則樹退化為鏈時遞歸深度 O(n) 導致 TLE 或棧溢出。