题解「JOISC 2019 Day3」指定城市

今天模拟赛的 C 题。

树上每条路每个方向有一个费用,在树上选中一个点可以使所有道路中朝向该点的费用全部不计,\(q\) 个询问,每次询问取 \(x\) 个点后树的最小费用。

赛时写了个暴力又写了个 \(x=1\) 的换根 dp,可惜全部挂分了。其实 \(x=2\) 的点也非常的好拿,像求树的直径一样维护一个离每个点最远的距离就可以了,赛场上居然没有想到这一点。

对于正解,首先得知道一个神奇的性质:对于 \(x \ge 2\)\(x+1\) 的最优方案一定包含了 \(x\) 中选的那些点。原因大概是选了两个点以后每添加一个点新的贡献只有以两个点中一个为根新点的深度,如果你每次加个最大的那很好,如果把原来的破坏掉的话只有破坏掉开始的两个点才有可能成立(因为两个点之后就一直选了最大的),而破坏之后的那个差一定不会被补得更多,充其量是一样。(口胡的)

注意这个性质必须得 \(x \ge 2\),赛场上猜到了性质,但是找了个 \(x=1\) 的反例(

然后就维护最大的深度就可以了,可以用线段树在 dfs 序上维护最大值,每次更新的时候暴力跳边把每条边在下面的子树中减掉并标记点,每个点只被删除一次,所以总复杂度是对的。

代码实现起来还是比较清晰的,虽然有一点点长。线段树修改的时候 += 写了 = 调了一节课多

代码
#include <iostream>
#include <utility>
#include <algorithm>
#define int long long
const int N = 200005, M = 2*N;
int n, x, y, ans[N], c, d, head[N], next[M], vet[M], dm[N], sm[N], tot,
	len[M], up[N], tag[4*N], f[N], num = 1, pa[N], pl[N], gin[N], rt, dep[N],
    size[N], q, sum, ord[N];
bool vis[N];
std::pair<int, int> t[N*4];
void add(int u, int v, int c) {
	vet[++num] = v, len[num] = c;
	next[num] = head[u];
	head[u] = num;
}
void pre(int u, int fa) {
	dm[u] = sm[u] = 0;
	for (int i = head[u]; i; i = next[i]) {
		int v = vet[i];
		if (v == fa) continue;
		f[1] = f[1] + len[i^1];
		pre(v, u);
		if (dm[v]+len[i] > dm[u]) sm[u] = dm[u], dm[u] = dm[v]+len[i];
		else if (dm[v]+len[i] > sm[u]) sm[u] = dm[v]+len[i];
	}
}
void dfs(int u, int fa) {
	ans[1] = std::max(ans[1], f[u]);
	int an = f[u] + std::max(dm[u], up[u]);
	if (an > ans[2]) ans[2] = an, rt = u;
	for (int i = head[u]; i; i = next[i]) {
		int v = vet[i];
		if (v == fa) continue;
		f[v] = f[u] - len[i^1] + len[i];
		if (dm[u] == dm[v]+len[i]) up[v] = std::max(up[u], sm[u]) + len[i^1];
		else up[v] = std::max(up[u], dm[u]) + len[i^1];
		dfs(v, u);
	}
}
void get(int u, int fa, int d) {
	dep[u] = d, size[u] = 1, gin[u] = ++tot, ord[tot] = u;
	pa[u] = fa;
	for (int i = head[u]; i; i = next[i]) {
		int v = vet[i];
		if (v == fa) continue;
		pl[v] = i;
		get(v, u, d+len[i]);
		size[u] += size[v];
	}
}
void pushup(int p) { t[p] = std::max(t[p+p], t[p+p+1]); }
void pushdown(int p) {
	int z = tag[p];
	t[p+p].first -= z, tag[p+p] += z;
	t[p+p+1].first -= z, tag[p+p+1] += z;
	tag[p] = 0;
}
void build(int p, int l, int r) {
	if (l == r) return t[p] = std::make_pair(dep[ord[l]], ord[l]), void();
	int mid = l + (r-l) / 2;
	build(p+p, l, mid), build(p+p+1, mid+1, r);
	pushup(p);
}
void update(int p, int l, int r, int x, int y, int z) {
	if (l == x && r == y) {
		t[p].first -= z, tag[p] += z;
		return;
	}
	pushdown(p);
	int mid = l + (r-l) / 2;
	if (y <= mid) update(p+p, l, mid, x, y, z);
	else if (x > mid) update(p+p+1, mid+1, r, x, y, z);
	else update(p+p, l, mid, x, mid, z), update(p+p+1, mid+1, r, mid+1, y, z);
	pushup(p);
}
void del(int u) {
	while ("twt-tec yyds"[0]) {
		if (pa[u] == 0 || vis[u]) break;
		vis[u] = 1;
		update(1, 1, n, gin[u], gin[u]+size[u]-1, len[pl[u]]);
		u = pa[u];
	}
}
signed main() {
	std::cin >> n;
	for (int i = 1; i < n; i++) {
		std::cin >> x >> y >> c >> d;
		add(x, y, c), add(y, x, d);
		sum += c + d;
	}
	pre(1, 0), dfs(1, 0);
	get(rt, 0, 0);
	build(1, 1, n);
	del(t[1].second);
	for (int i = 3; i <= n; i++) {
		ans[i] = ans[i-1] + t[1].first;
		del(t[1].second);
	}
	std::cin >> q;
	while (q --) {
		std::cin >> x;
		std::cout << sum-ans[x] << '\n';
	}
}
posted @ 2021-08-18 20:03  Acfboy  阅读(90)  评论(0编辑  收藏  举报