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;
}
posted @ 2020-05-19 17:09  Hs-black  阅读(239)  评论(0编辑  收藏  举报