分析
首先发现此题的式子一看就不是很友好。所以尝试化简。
原式:\(max(|a_u + a_v|, |a_u - a_v|)\)。
分类讨论:
-
当 \(a_u > 0, a_v > 0\) 时,显然有 原式 \(= a_u + a_v\);
-
当 \(a_u > 0, a_v < 0\) 时,
\(|a_u - a_v| = |a_u + |a_v||\),
\(|a_u + a_v| = |a_u - |a_v||\)。
肉眼可见,第一个式子大于第二个式子。即 原式 \(= a_u + |a_v|\); -
当 \(a_u < 0, a_v > 0\) 时,
\(|a_u + a_v| = |a_v - |a_u|| = |-(|a_u| - a_v)| = ||a_u| - a_v|\),
\(|a_u - a_v| = |-a_v - |a_u|| = |-(|a_u| + a_v)| = ||a_u| + a_v|\),
这样就跟上一种情况差不多,即 原式 \(=|a_u| + a_v\); -
当 \(a_u < 0, a_v < 0\) 时,显然有 原式 \(=|a_u| + |a_v|\)。
注意到在以上分类讨论的结果中,没有被开绝对值的数都是正数,而正数的绝对值等于其自身。故 原式 \(= |a_u| + |a_v|\)。比原式漂亮多了。
这样,我们发现询问时的答案与每个点原本的点权无关,只与其原本点权的绝对值有关。所以可以把所有点的点权 \(a_i\) 改为 \(|a_i|\),而不对答案造成影响。以下所述 \(a_i\) 也即为 \(|a_i|\)。
到这里,我们就将每一条边的边权转换到了其两个端点上。题目所要求的求两点间最短路的距离也可以转换到两点间最短路上所有点的点权上。故我们就可以只维护树上每两点间的点权和即可。考虑树链剖分。每次询问时,设询问的两点为 \(u\) 和 \(v\),则我们只需要求出 \(u\) 和 \(v\) 之间最短路上的点权和 \(s\),再输出最终答案 \(s * 2 - a_u - a_v\) 即可。
至于答案为什么是这个……因为在最短路上的每个点(除了起点和终点)的点权要被算两遍(因为两侧共有两条边),而起点和终点就只用被算一次。
代码
#include <iostream>
#define int long long
#define abs(x) ((x) > 0 ? (x) : -(x))
using namespace std;
const int N = 1048576; // ???
int head[200005], nxt[200005], to[200005], cnt; // 链式前向星
void add(int u, int v) { to[++cnt] = v, nxt[cnt] = head[u], head[u] = cnt; }
int dfn[100005], sz[100005], son[100005], dep[100005], top[100005], f[100005];
int w[N * 4], wt[N * 4], ncnt;
// ----------------- 以下树剖板子 --------------------
void dfs1(int x, int fa, int d) {
dep[x] = d;
f[x] = fa;
sz[x] = 1;
for (int i = head[x]; i != 0; i = nxt[i]) {
int v = to[i];
if (v != fa) {
dfs1(v, x, d + 1);
sz[x] += sz[v];
if (sz[v] > sz[son[x]])
son[x] = v;
}
}
}
void dfs2(int x, int t) {
dfn[x] = ++ncnt;
wt[ncnt] = w[x];
top[x] = t;
if (!son[x])
return;
dfs2(son[x], t);
for (int i = head[x]; i != 0; i = nxt[i]) {
int v = to[i];
if (v != son[x] && v != f[x])
dfs2(v, v);
}
}
// ----------------- 以上树剖板子 --------------------
// ---------------- 以下线段树板子 -------------------
int sm[N * 4];
void Build(int o, int l, int r) {
if (l == r) {
sm[o] = wt[l];
return;
}
int mid = l + r >> 1;
Build(o << 1, l, mid);
Build(o << 1 | 1, mid + 1, r);
sm[o] = sm[o << 1] + sm[o << 1 | 1];
}
void Add(int o, int l, int r, int x, int y) {
if (l == r) {
sm[o] = y;
return;
}
int mid = l + r >> 1;
if (x <= mid)
Add(o << 1, l, mid, x, y);
else
Add(o << 1 | 1, mid + 1, r, x, y);
sm[o] = sm[o << 1] + sm[o << 1 | 1];
}
int Query(int o, int l, int r, int L, int R) {
if (L <= l && r <= R)
return sm[o];
int mid = l + r >> 1, ret = 0;
if (L <= mid)
ret += Query(o << 1, l, mid, L, R);
if (R > mid)
ret += Query(o << 1 | 1, mid + 1, r, L, R);
return ret;
}
// ---------------- 以上线段树板子 -------------------
// --------------- 以下继续树剖板子 ------------------
void change(int u, int v) { Add(1, 1, N, dfn[u], v); }
int Query_sum(int u, int v) {
int ret = 0;
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]])
swap(u, v);
ret += Query(1, 1, N, dfn[top[u]], dfn[u]);
u = f[top[u]];
}
if (dep[u] > dep[v])
swap(u, v);
ret += Query(1, 1, N, dfn[u], dfn[v]);
return ret;
}
// --------------- 以上继续树剖板子 ------------------
signed main() {
int n, q;
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> w[i], w[i] = abs(w[i]); // 取绝对值
for (int i = 0, u, v; i < n - 1; i++) {
cin >> u >> v;
add(u, v);
add(v, u);
}
dfs1(1, 0, 1);
dfs2(1, 1);
Build(1, 1, N);
while (q--) {
int op;
int x, y;
cin >> op >> x >> y;
if (op == 1)
change(x, abs(y)); // 修改的时候把将要改成的数也取一个绝对值
else
cout << Query_sum(x, y) * 2 // 这甚至是可以编译通过的(
- Query(1, 1, N, dfn[x], dfn[x])
- Query(1, 1, N, dfn[y], dfn[y])
<< "\n";
}
return 0;
}
标签:sz,head,Illusions,int,点权,son,100005,CF1575I,Desert
From: https://www.cnblogs.com/forgotmyhandle/p/18000228