CF566C Logistical Questions(重心分治)
CF566C Logistical Questions(重心分治)
题目大意
- 一棵 \(n\) 个节点的树,点有点权,边有边权。
- 两点间的距离定义为两点间边权和的 \(\frac 32\) 次方。
- 求这棵树的带权重心。
- \(n \le 2 \times 10^5\)。
解题思路
以点 i 为中心的函数 \(f_i(x) = dis^{1.5}_{i \to x} * w[i]\),不难发现它是一个下凸函数,那么当重心为 x 时候的答案为 $ans(x) = \sum_{i=1}^n f_i(x) $ 由于凸函数相加还是凸函数,因此 \(ans(x)\) 也是凸函数,因此从一个点出发最多只有一个方向可以使答案变的更优,如何判断,众所周知,可以用导数来表示函数的变化情况,我们可以把每个子树的导数都求出来相加,如果 \(\sum_{i \in sons(x)}p(i) - 2 * p(j) \le 0\),说明 j 所在的子树有更优的答案,像点分治一样跳过去即可
时间复杂度 \(\Theta(NlogN)\)
带码 (点分治我总会犯奇奇怪怪的错误/kk
const int N = 200500;
int h[N], ne[N<<1], to[N<<1], w[N<<1], tot, P, n;
double ans = 1e20;
inline void add(int x, int y, int z) {
ne[++tot] = h[x], to[h[x] = tot] = y, w[tot] = z;
}
int siz[N], vis[N], val[N], Siz, lim, rt;
void get_rt(int x, int fa) {
siz[x] = 1; int mx = 0;
for (int i = h[x]; i; i = ne[i]) {
int y = to[i]; if (y == fa || vis[y]) continue;
get_rt(y, x), siz[x] += siz[y]; Mx(mx, siz[y]);
}
Mx(mx, Siz - siz[x]);
if (mx < lim) lim = mx, rt = x;
}
double d[N], sum, sumd;
void dfs(int x, int fa, int dis, int num) {
sum += pow(dis, 1.5) * val[x], d[num] += pow(dis, 0.5) * 1.5 * val[x];
for (int i = h[x], y; i; i = ne[i])
if ((y = to[i]) != fa) dfs(y, x, dis + w[i], num);
}
void solve(int x) {
if (vis[x]) return;
get_rt(x, 0), vis[x] = 1, sum = sumd = 0;
for (int i = h[x]; i; i = ne[i]) {
int y = to[i]; d[y] = 0;
dfs(y, x, w[i], y), sumd += d[y];
}
if (sum < ans) ans = sum, P = x;
for (int i = h[x]; i; i = ne[i]) {
int y = to[i];
if (sumd - d[y] * 2 >= 0) continue;
Siz = siz[y], lim = 998224353, get_rt(y, 0);
solve(rt); break;
}
}
int main() {
read(n);
for (int i = 1;i <= n; i++) read(val[i]);
for (int i = 1, x, y, z; i < n ; i++)
read(x), read(y), read(z), add(x, y, z), add(y, x, z);
Siz = n, lim = 1919810, get_rt(1, 0), solve(rt);
printf ("%d %.8lf\n", P, ans);
return 0;
}