修马路

问题描述

⼩迟⽣活的城市是⼀棵树(树指的是⼀个含有 (n) 个节点以及 (n-1) 条边的⽆向连通图),节点编号从 (1)(n),每条边拥有⼀个权值 (value),表⽰通过这条边的时候你需要交纳的⾦钱(注意,有可能这个值为负数,也就是说你经过这条边的时候你可以赚钱)

⼩迟是⼀个杰出的马路⼯,他居住在节点 (s),他能够选择任意⼀个节点(m),并将从节点 (s) 到节点 (m) 的简单路径(简单路径指的是不经过同⼀个节点两次)上的所有边的权值都修改为 (0).

现在⼩迟获得 (q) 个请求,每个请求都是以 (a, b) 的形式给出,表⽰⼩迟的好朋友⼩早希望从节点 (a) ⾛简单路径到节点 (b),⼩迟希望能最⼩化⼩早需要缴纳的钱。

需要注意的是,⼩迟获得的这 (q) 个请求是相互独⽴的,也就是说您只需要对于每⼀个请求,决定⼩迟的⼀个修路⽅案,使得⼩早需要缴纳的钱尽可能的少。

输入格式

输⼊⽂件名为 road.in。

第⼀⾏三个正整数为 (n,q,s)

接下来 (n-1) ⾏,每⾏三个整数 (x y z), 表⽰有⼀条边 ((x,y))(value)(z)

接下来 (q) ⾏,每⾏两个正整数 (a, b),表⽰请求。

输出格式

输出⽂件名为 road.out。

Q ⾏,每⾏⼀个整数,表⽰需要缴纳的最少的钱。

样例输入

3 2 1
1 2 1
2 3 1
1 2
1 3

样例输出

0
0

样例解释

对于第⼀次询问 (1,2), ⼩迟可以修从 (1)(2) 的路,从⽽使得⼩早不需要缴纳⾦钱;

对于第⼆次询问 (1,3), ⼩迟可以修从 (1)(3) 的路,从⽽使得⼩早不需要缴纳⾦钱。

数据规模及约定

对于 30% 的数据,(n≤1000,q≤1000)

对于 100% 的数据,(1≤n,q≤200000,1≤x,y≤n,|z|≤1000)

题目分析

题目给的是一颗无根树,如果我们以小迟的家作为根的话,这个问题相当于你可以删去根到任意节点的权值, 使得 (a)(b) 花费最少

看到树上的简单路径,就知道这道题一定考察 LCA

算法一: 在树上倍增求LCA的基础上, 同时维护倍增的最大和, 复杂度 (nlogn), 然而我考场上想出来没Debug出来

算法二:考虑链上问题, 即求链上最大值。在树链剖分上线段树维护最大值就好了, 复杂度 (nlog^2n)(远远无法达到理论上界)

#include <cstdio>

int read() {
	int x = 0, ch = getchar(), f = 0;
	for (; ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') f = 1;
	for (; ch >= '0' && ch <= '9'; ch = getchar())
		x = (x << 1) + (x << 3) + (ch ^ 48);
	return f ? -x : x;
}

int max(const int &x, const int &y) {
	return x > y ? x : y;
}

#define rep(i, s, t) for (int i = (int)(s); i <= (int)(t); ++ i)

const int maxn = 2e5 + 10;

int ver[maxn << 1], Next[maxn << 1], head[maxn], edge[maxn << 1], tot;
int sz[maxn], dep[maxn], son[maxn], fa[maxn], pos[maxn], tid[maxn], a[maxn], dfn, top[maxn];

int n, m, root;

inline void addline(int x, int y, int z) {
	ver[++tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot;
}

inline void swap(int &x, int &y) {
	int t = y; y = x; x = t;
}

struct SegmentTree {
	int l, r, val;
	SegmentTree() {
		val = -1e9; l = r = 0;
	}
}tree[maxn << 2];

inline void build(int k, int l, int r) {
	tree[k].l = l, tree[k].r = r;
	if (l == r) {
		tree[k].val = a[pos[l]]; return;
	}
	int mid = (l + r) >> 1;
	build(k << 1, l, mid); build(k << 1 | 1, mid + 1, r);
	tree[k].val = max(tree[k << 1].val, tree[k << 1 | 1].val);
}

int query(int k, int l, int r) {
	if (tree[k].l == l && tree[k].r == r) {
		return tree[k].val;
	}
	int mid = (tree[k].l + tree[k].r) >> 1;
	if (mid >= r) {
		return query(k << 1, l, r);
	} else if (mid < l) {
		return query(k << 1 | 1, l, r);
	} else {
		return max(query(k << 1, l, mid), query(k << 1 | 1, mid + 1, r));
	}
}

inline void dfs1(int x, int f, int d, int val) {
	sz[x] = 1, fa[x] = f, dep[x] = d, a[x] = val;
	for (int i = head[x], y; i; i = Next[i]) {
		if ((y = ver[i]) == f) continue;
		dfs1(y, x, d + 1, val + edge[i]); sz[x] += sz[y];
		if (sz[y] > sz[son[x]]) son[x] = y;
	}
}

inline void dfs2(int x, int rt) {
	top[x] = rt, pos[tid[x] = ++dfn] = x;
	if (son[x]) dfs2(son[x], rt);
	for (int i = head[x], y; i; i = Next[i]) {
		if ((y = ver[i]) != fa[x] && y != son[x]) dfs2(y, y);
	}
}

int lca(int x, int y) {
	if (top[x] == top[y]) return dep[x] < dep[y] ? x : y;
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]]) swap(x, y);
		x = fa[top[x]];
	}
	return dep[x] < dep[y] ? x : y;
}

inline void Pre() {
	dfs1(root, root, 0, 0), dfs2(root, root), build(1, 1, n);
}

int jump(int f, int t) {
	if (top[f] == top[t]) {
		return query(1, tid[t], tid[f]);
	}
	int ans = -1e9;
	while (top[f] != top[t]) {
		ans = max(ans, query(1, tid[top[f]], tid[f]));
		f = fa[top[f]];
	}
	ans = max(ans, query(1, tid[t], tid[f]));
	return ans;
}

int query(int x, int y) {
	int Lca = lca(x, y), aa = jump(x, Lca) - a[Lca], ba = jump(y, Lca) - a[Lca];
	int ans = a[x] + a[y] - 2 * a[Lca];
	aa = max(aa, 0); ba = max(ba, 0);
	return ans - max(aa, ba);
}

int main() {
	n = read(), m = read(), root = read();
	int x, y, z;
	rep(i, 1, n - 1)
		x = read(), y = read(), z = read(),
		addline(x, y, z), addline(y, x, z);
	Pre();
	rep(i, 1, m) {
		x = read(), y = read();
		printf("%d
", query(x, y));
	}
}
原文地址:https://www.cnblogs.com/Alessandro/p/9929744.html