Loading

【题解】「JOISC 2019 Day3」指定城市

给定一棵树,双向边,每条边两个方向的权值分别为 \(C_i, D_i\),多次询问 \(k\),表示选出 \(k\) 个点,依次将以每个点为根的内向树边权赋值为 \(0\),需要求出最后树的边权之和的最小值。

\(k=1\) 的时候,我们求出 \(w_x\) 表示以 \(x\) 为根的内向树边权和,总和减去 \(\max\{w\}\) 即为答案,\(w\) 可以用换根 DP 求得。

考虑 \(k > 1\) 的情况,有一个关键结论是询问 \(k + 1\) 的答案一定是 \(k\) 的答案基础上,加上一个点。

注意当 \(k = 1\) 的时候结论不成立!只有 \(k > 1\) 时结论成立。我开始猜了这个结论敲暴力验证了一下 \(k = 1 \to k =2\) 发现不满足条件就排除了,结果裂开。以后做这种结论题还要考虑到 corner case。

我们先考虑 \(k = 2\) 怎么做,不难直接推出如果选择两个点 \(x,y\),最大删除的边的和为 \(\dfrac{w_x + w_y + dis(x,y)}{2}\),其中 \(dij(x,y)\) 表示 \(x,y\) 之间路径的双向边权和。这个式子是直径的格式,直接二次扫描换根可以求得。

然后在这条直径的基础上增加点,就相当于将直径缩成一个点,并以之为根,每次增加一条根到叶子的路径。这是个经典问题,直接长链剖分后排序选择即可。

那么这个关键结论怎么证明呢,因为 \(k = 2\) 时选择的是直径,所以在此基础上新增一个点,不会修改原直径,因为不可能有比直径更长的路径。\(k = 1\) 就纯属只存在一个点的特例,所以不满足结论。

时间复杂度 \(\mathcal{O}(N\log N)\),瓶颈在于排序,基数排序可以优化至线性。

#define N 200005
int n, m, h[N], tot = 1; LL w[N], sum, ed[N], d[N], f[N], v[N];
struct edge{int to, nxt, val;}e[N << 1];
void add(int x,int y,int z){e[++tot].nxt = h[x], h[x] = tot, e[tot].to = y, e[tot].val = z;}
void dfs(int x,int fa){for(int i = h[x]; i; i = e[i].nxt)if(e[i].to != fa)dfs(e[i].to, x), w[x] += w[e[i].to] + e[i].val;}
void calc(int x,int fa){for(int i = h[x]; i; i = e[i].nxt)if(e[i].to != fa)w[e[i].to] = w[x] - e[i].val + e[i ^ 1].val, calc(e[i].to, x);}
void Dfs(int x,int fa){f[x] = fa; for(int i = h[x]; i; i = e[i].nxt)if(e[i].to != fa)d[e[i].to] = d[x] + e[i].val + e[i ^ 1].val, Dfs(e[i].to, x);}
vector<LL>c;
LL solve(int x,int fa){
	LL cur = 0;
	for(int i = h[x]; i; i = e[i].nxt)if(e[i].to != fa){
		LL w = e[i ^ 1].val + solve(e[i].to, x);
		if(!cur)cur = w;
		else c.pb(min(cur, w)), cmx(cur, w);
	}return cur;
}
int main() {
	read(n);
	rp(i, n - 1){
		int x, y, l, r;
		read(x, y, l, r), sum += l + r;
		add(x, y, r), add(y, x, l);
	}
	dfs(1, 0), calc(1, 0);
	rp(i, n)cmx(ed[1], w[i]);
	Dfs(1, 0); int A = 1;
	rp(i, n)if(d[i] + w[i] > d[A] + w[A])A = i;
	d[A] = 1; Dfs(A, 0); int B = 1;
	rp(i, n)if(d[i] + w[i] > d[B] + w[B])B = i;
	ed[2] = (d[B] + w[A] + w[B]) / 2; int x = B;
	while(x)v[x] = 1, x = f[x];
	x = B; while(x){
		for(int i = h[x]; i; i = e[i].nxt)if(!v[e[i].to])
			c.pb(solve(e[i].to, x) + e[i ^ 1].val);
		x = f[x];
	}
	sort(c.begin(), c.end()), reverse(c.begin(), c.end());
	int t = 2;
	go(x, c)ed[t + 1] = ed[t] + x, t++;
	while(t < n)ed[t + 1] = ed[t], t++;
	read(m); while(m--){int x; read(x); printf("%lld\n", sum - ed[x]);}
	return 0;
}
posted @ 2022-05-28 11:05  7KByte  阅读(79)  评论(0编辑  收藏  举报