【题解】CF566C-Logistical Questions
给定一颗树,距离定义为边权和的 \(\frac{3}{2}\) 次方,求树的带权中心
如果我们距离的定义为边权和,显然我们可以指定一个点,然后一直调整到最优。
我们可以延续这个思想,我们从任意一个点开始,存在且恰好存在一条出边使得代价更小,我们一直沿着这条边走就行。
那么我们如何快速求出这条边呢,我们可以只向该方向走一个极小的值,那么该方向子树内为 \(\sum w_i (l_i - \Delta)^{\frac{3}{2}}\),其余的贡献为 \(\sum w_i(l_i+\Delta)^{\frac{3}{2}}\)。
现在令 \(\lim \Delta \to 0\),那么就是对函数 \(\sum w_i l_i^{\frac{3}{2}}\) 求导,所以我们要求的就是子树内 \(\sum \frac{3}{2}w_i\sqrt l_i\)。
由于根号的存在,每移动一次,所有的 \(\sqrt{l}\) 的变化无法快速计算,所以每次都需要 \(\mathcal{O}(N)\) 重新统计代价。不过有一个经典的 trick 就是直接点分治,每次只递归一半即可。复杂度和序列分治一样。
#define N 200005
int n, a[N], rt, w, mn, sz[N], v[N];
vector<Pr>e[N];
void dfs(int x,int fa){
sz[x] = 1; int cur = 0;
go(y, e[x])if(!v[y.fi] && y.fi != fa)
dfs(y.fi, x), sz[x] += sz[y.fi], cmx(cur, sz[y.fi]);
cmx(cur, w - sz[x]);
if(cur < mn)mn = cur, rt = x;
}
long double sum, ans = 1e60, u[N]; int ed;
void calc(int x,int fa,int ds){
sum += pow(ds, 1.5) * a[x];
u[x] = 1.5 * a[x] * sqrt(ds);
go(y, e[x])if(y.fi != fa)calc(y.fi, x, ds + y.se), u[x] += u[y.fi];
}
void solve(int x,int s){
rt = x, mn = w = s, dfs(x, 0);
x = rt, sum = 0, calc(x, 0, 0);
if(sum < ans)ans = sum, ed = x;
v[x] = 1;
go(y, e[x])if(!v[y.fi] && u[x] - u[y.fi] * 2 < 0){
solve(y.fi, sz[y.fi] > sz[x] ? s - sz[x] : sz[y.fi]);
break;
}
}
int main() {
read(n);
rp(i, n)read(a[i]);
rp(i, n - 1){
int x, y, z;
read(x, y, z);
e[x].pb(mp(y, z)), e[y].pb(mp(x, z));
}
solve(1, n);
printf("%d %.7Lf\n", ed, ans);
return 0;
}