修马路
问题描述
⼩迟⽣活的城市是⼀棵树(树指的是⼀个含有 \(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\n", query(x, y));
}
}