点分树模版 / 震波

一颗 \(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);
		}
	}
}

posted @ 2026-08-30 09:56  Denia-kawaii  阅读(9)  评论(0)    收藏  举报