[POJ2763] Housewife Wind 题解
[POJ2763] Housewife Wind 题解
题目简述
给一棵有 \(n\) 个点的带权树,\(q\) 次询问,每次两种操作。
- 查询树上两点最短距离
- 修改一条边的边权
\(n \le 10^5 , q \le 10^5\)
想法
如果只看操作一,明显可以用 \(\text{LCA}\) 求解,具体步骤是:
-
预处理所有点到根的距离 \(dist_i\)
-
输入 \(u, v\),求得 \(\text{LCA}(u, v)\),距离即为
\[dist_u + dist_v - 2dist_{LCA} \]
但是此时多了一个修改边权的操作,考虑如果修改一条边的边权 ,哪些点到根的距离会发生改变。
答:这条边通向的节点的子树。
但是如果暴力修改明显不可接受,这里有一种常见的trick:
通过 \(\text{Dfs}\) 序将整棵树转变为一串序列,利用数据结构方便地维护序列中的区间信息。
具体而言就是把 \(\text{Dfs}\) 序访问的节点依次记录下来,构成一个访问序列。
对于这题而言,我们需要在序列区间上进行 区间修改,改变他们到根节点的距离。然后进行 单点查询,分别查询 \(u, v, lca\) 到根的距离。
对于这个 区间修改,单点查询 的问题,可以用 BIT 或者线段树简单维护,这题卡得比较严,我采用了常数较小、较容易实现的 BIT。
// Problem: Housewife Wind
// Contest: POJ - POJ Monthly--2006.02.26
// URL: http://poj.org/problem?id=2763
// Memory Limit: 65 MB
// Time Limit: 4000 ms
// Author: Moyou
// Copyright (c) 2022 Moyou All rights reserved.
// Date: 2023-01-18 11:42:22
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
const int N = 2e5 + 10;
int n, q, s;
struct edge
{
int v, w, id;
};
vector<edge> g[N];
int lpos[N << 2], rpos[N << 2], id[N << 2], idx, val[N << 2];
struct BIT
{
int tr[N];
int lowbit(int x)
{
return x & -x;
}
void update(int i, int c) // 位置x加c
{
for (; i <= (idx); i += lowbit(i))
tr[i] += c;
}
int query(int i) // 返回前x个数的和
{
int res = 0;
for (; i; i &= i - 1) // 等价于i -= lowbit(i)
res += tr[i];
return res;
}
} t; // 树状数组板
int f[N][23], depth[N];
void get_depth(int u, int fa, int c) // 倍增LCA板 + 维护序列信息
{
depth[u] = depth[fa] + 1, f[u][0] = fa;
for (int i = 1; i <= 20; i++)
f[u][i] = f[f[u][i - 1]][i - 1];
for(int j = 0; j < g[u].size(); j ++)
{
int i = g[u][j].v, c = g[u][j].w, x = g[u][j].id;
if (i == fa)
continue;
lpos[x] = id[i] = ++ idx; // 记录边与点在序列中的编号
get_depth(i, u, c);
rpos[x] = ++ idx;
}
}
int Lca(int a, int b) // 倍增
{
if (depth[a] > depth[b])
swap(a, b);
for (int i = 20; i >= 0; i--)
if (depth[f[b][i]] >= depth[a])
b = f[b][i];
if (a == b)
return a;
for (int i = 20; i >= 0; i--)
if (f[b][i] != f[a][i])
b = f[b][i], a = f[a][i];
return f[a][0];
}
edge make(int a, int b, int c)
{
edge tmp;
tmp.v = a, tmp.w = b, tmp.id = c;
return tmp;
}
inline char get_char() // 加一点小小的卡常🤏
{
static char buf[1000000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++;
}
inline int read()
{
int x = 0;
char ch = get_char();
while (ch < '0' || ch > '9')
ch = get_char();
while (ch <= '9' && ch >= '0')
x = (x << 1) + (x << 3) + (ch ^ 48), ch = get_char();
return x;
}
int main()
{
n = read(), q = read(), s = read();
for (int i = 1; i < n; i++)
{
int a = read(), b = read(), w = read();
g[a].push_back(make(b, w, i));
g[b].push_back(make(a, w, i));
val[i] = w;
}
get_depth(1, 0, 0);
for(int i = 1; i < n; i ++) // 差分树状数组
t.update(lpos[i], val[i]), t.update(rpos[i], -val[i]);
while (q--)
{
int op = read();
if (op)
{
int u = read(), tmp = read();
int l = lpos[u], r = rpos[u];
t.update(l, tmp - val[u]), t.update(r, val[u] - tmp); // 因为是修改而不是增加需要改变一下
val[u] = tmp;
}
else
{
int b = read();
int lca = Lca(s, b);
printf("%d\n", t.query(id[s]) + t.query(id[b]) - 2 * t.query(id[lca]));
s = b; // 不要忘了结合题目,出发点是改变的
}
}
// cout << ' ';
return 0;
}