Loading

【题解】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;
}
posted @ 2022-05-07 16:12  7KByte  阅读(79)  评论(0编辑  收藏  举报