点分树模版 / 震波
一颗 \(n\) 个节点的数,每个节点有一个权值 \(a_i\),有 \(q\) 次操作:
0 x k查询与 \(x\) 点距离不超过 \(k\) 的所有点的权值,即 \(\sum\limits_{dis(x,y)\le k}a_y\)。1 x v将 \(x\) 点的权值该为 \(v\)。
回想点分治,其思想是通过每次递归重心的方式查询每一颗子树的答案,总时间复杂度为 \(O(nlogn)\)。
考虑保留点分治递归留下的搜索树,也就是点分树,这个树的高度为 \(logn\),且显然满足点分治的优秀性质。
对于查询操作,枚举 \(x\) 与 \(y\) 的 lca \(z\),条件转化为:
\[\sum\limits_{dis(x,z)+dis(z,y)\le k}a_y\Rightarrow \sum\limits_{dis(z,y)\le k-dis(x,z)}a_y
\]
所以维护 \(A_{i,j}\) 表示 \(i\) 子树内与 \(i\) 距离 \(j\) 的点权和,查询时统计前缀和 \(\sum\limits_{j=0}^{k-dis(x,z)}A_{i,j}\) ,用树状数组实现即可。
但要注意重复问题,减去 \(x\) 方向子树对 \(u\) 的贡献。
警告:
- 容斥时不能直接 \(A_{fa_i,j}-A_{i,j-1}\) 因为点分树父子关系被打乱,建立两个树状数组分别统计对自己以及父亲节点的贡献。
- 每个节点开两个树状数组会超空间,用
vector开树状数组。 - 树状数组大小初始化为 \(sz_{rt}\),必须先搜索重心 \(rt\) 后更新。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5;
namespace DS {
struct BIT {
#define lowbit(x) (x & (-x))
vector<int> tr;
void setsize(int n) {
tr.assign(n + 2, 0);
}
void update(int p, int v) {
p ++;
for (; p < (int)tr.size(); p += lowbit(p)) tr[p] += v;
}
int prefix(int p) {
if (p < 0 || tr.empty()) return 0;
int ans = 0;
p ++;
p = min(p,(int)tr.size() - 1);
for (; p; p -= lowbit(p)) ans += tr[p];
return ans;
}
};
}
namespace SilverWolf {
int n, q;
int a[maxn + 5];
vector<int> G[maxn + 5];
int Tfa[maxn + 5]; // 点分树父子关系
int vis[maxn + 5];
int rt = 0, totsz = 0;
int sz[maxn + 5], mxsz[maxn + 5];
void getsz(int u, int fa) {
sz[u] = 1;
for (int v : G[u]) {
if (v == fa || vis[v]) continue;
getsz(v, u);
sz[u] += sz[v];
}
}
void gethv(int u, int fa) {
sz[u] = 1, mxsz[u] = 0;
for (int v : G[u]) {
if (v == fa || vis[v]) continue;
gethv(v, u);
sz[u] += sz[v];
mxsz[u] = max(mxsz[u], sz[v]);
}
mxsz[u] = max(mxsz[u], totsz - sz[u]);
if (mxsz[rt] > mxsz[u]) rt = u;
}
DS::BIT all[maxn + 5],sub[maxn + 5]; // 注意必须是两个 all[fa[u]] - all[u] 是不可取的,因为父子关系被打乱
/*
bit[0][u].[i]: 子树u内到u距离为i的点权和
bit[1][u].[i]: u子树内到 Tfa[u]的贡献
*/
vector<int> fdis[maxn + 5]; // 节点u到所有点分树祖先的距离
void cal(int rt, int u, int fa, int dep) { // 计算rt子树: dis(u,v) v为u的子树内节点
all[rt].update(dep, a[u]);
fdis[u].push_back(dep);
for (int v : G[u]) {
if (v == fa || vis[v]) continue;
cal(rt, v, u, dep + 1);
}
}
void calsub(int rt, int u, int fa, int dep) { // 计算rt子树: dis(u,v) v为u的子树内节点
sub[rt].update(dep, a[u]);
for (int v : G[u]) {
if (v == fa || vis[v]) continue;
calsub(rt, v, u, dep + 1);
}
}
void build(int u) {
vis[u] = 1;
cal(u, u, 0, 0);
for (int v : G[u]) {
if (vis[v]) continue;
getsz(v, u);
totsz = sz[v];
rt = 0, mxsz[rt] = n;
gethv(v, u);
int son = rt;
Tfa[son] = u;
all[son].setsize(totsz);
sub[son].setsize(totsz);
calsub(son, v, u, 1);
build(rt);
}
}
int query(int x,int k) {
int ans = 0,u = x;
for (int i = 0;i < (int)fdis[x].size();i ++) {
ans += all[u].prefix(k - fdis[x][i]);
if (Tfa[u] != 0) ans -= sub[u].prefix(k - fdis[x][i + 1]);
u = Tfa[u];
}
return ans;
}
void modify(int x,int v) {
int d = v - a[x],u = x;
for (int i = 0;i < (int)fdis[x].size();i ++) {
all[u].update(fdis[x][i],d);
if (Tfa[u] != 0) sub[u].update(fdis[x][i + 1],d);
u = Tfa[u];
}
a[x] = v;
}
int ans = 0;
void Main() {
cin >> n >> q;
for (int i = 1; i <= n; i ++) cin >> a[i];
for (int i = 1; i <= n - 1; i ++) {
int u, v;
cin >> u >> v;
G[u].push_back(v), G[v].push_back(u);
}
getsz(1, 0);
totsz = sz[1];
rt = 0, mxsz[rt] = n + 1;
gethv(1, 0);
all[rt].setsize(sz[rt]);
sub[rt].setsize(sz[rt]);
build(rt);
for (int i = 1; i <= n; i ++) reverse(fdis[i].begin(), fdis[i].end());
while (q --) {
int op, x, y;
cin >> op >> x >> y;
x ^= ans,y ^= ans;
if (op == 0) {
ans = query((int)x, y);
cout << ans << '\n';
} else modify((int)x, y);
}
}
}

浙公网安备 33010602011771号