CF566C Logistical Questions(重心分治)

CF566C Logistical Questions(重心分治)

题目大意

  • 一棵 (n) 个节点的树,点有点权,边有边权。
  • 两点间的距离定义为两点间边权和的 (frac 32) 次方。
  • 求这棵树的带权重心。
  • (n le 2 imes 10^5)

解题思路

以点 i 为中心的函数 (f_i(x) = dis^{1.5}_{i o 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
", P, ans);
	return 0;
}
原文地址:https://www.cnblogs.com/Hs-black/p/12918105.html