点分树
学名:\([\text{C}_6\text{H}_{10}\text{O}_5]_n\) tree
介绍
点分治的思路很简单,每次选重心,统计答案并分治。
点分树就是多维护了点分治的一个信息:每一层分治的重心记录它的上一个重心。
按这个关系建树,于是得到一个与原树形态“无关的”的新树。
这颗新树有如此的性质:
- 树上节点的深度 \(dep_i \le \lceil\log_2 n\rceil\)。即对树分层,总共只会有 \(\log n\) 层。
- 两个节点在新树上的 \(\mathrm{lca}\) 是原数上这两个节点路径上的某个点。
根据 1 性质,就发现点分树可以做各种一般树做不到的操作:
- 暴力跳到根,时间复杂度 \(\mathcal O(\log n)\)。
- 每个节点存储它子树中所有节点的信息,时空复杂度 \(\mathcal O(n\log n)\)(一般树要 \(n^2\),比如用一条链就可以卡)。
然后就可以做题了。
例题 1 震波
给出 \(n\) 个节点的点权树,\(m\) 次询问求到点 \(x\) 距离不超过 \(k\) 的节点权值之和,还有修改某个节点的点权。
在每个节点处以到该节点的距离为下标建线段树(或离散化),存储子树每个节点的权值,询问时暴力跳根统计,修改时暴力跳根对从该节点到根上所有点有关该点的信息更改即可。
几个细节:从 \(x\) 往上跳的过程中有重复计数。设当前跳到 \(y\),有可能统计到 \(x \to y \to z\) 这条原树上路径,然后加上 \(z\) 的点权,但是若 \(z\) 是 \(x\to y\) 在点分树上路径上的某个点,就可能重复统计,因此要删掉。
让我调了 2h+ 的竟是重剖时 dfs2 返祖了,当时用的 dep 来判节点是否被经过。
这题的 RE 一般都是求错了答案导致的,因为强制在线只保证解密后合法。
时间复杂度 \(\mathcal O(n \log n+q \log^2n)\),空间复杂度 \(\mathcal O(n \log n)\)。挺史的。写树状数组 + 离散化时空常数都要好一点。
#include <bits/stdc++.h>
using namespace std;
const int MAXSIZE = 1e6+5;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define per(i, r, l) for (int i = r; i >= l; --i)
#define LL long long
#define ULL unsigned long long
#define PII pair<int,int>
#define PLL pair<LL,LL>
// #define int long long
#define FASTIO ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
const int N = 1e5+5,mod = 998244353;
int n, m, a[N];
struct SegmentTree {
struct node {
int lc=0, rc=0, dat=0;
} tr[N*80];
#define dat(p) tr[p].dat
#define lc(p) tr[p].lc
#define rc(p) tr[p].rc
int tot;
void add(int &p, int l, int r, int x, int v) {
if (!p) p = ++tot;
dat(p)+=v;
if (l == r) return;
int mid = (l+r)>>1;
x <= mid ? add(lc(p), l, mid, x, v) : add(rc(p), mid+1, r, x, v);
}
int query(int p, int l, int r, int L, int R) {
if (!p) return 0;
if (L <= l && r <= R) return dat(p);
int mid = (l+r)>>1, res = 0;
if (L <= mid) res += query(lc(p), l, mid, L, R);
if (mid < R) res += query(rc(p), mid+1, r, L, R);
return res;
}
#undef lc
#undef rc
#undef dat
};
struct BetterSGT {
int rt[N];
SegmentTree sgt;
BetterSGT() { memset(rt, 0, sizeof(rt)); }
inline void add(int p, int x, int v) { sgt.add(rt[p], 0, n, x, v); }
inline int query(int p, int L, int R) { return R >= 0 ? sgt.query(rt[p], 0, n, L, R) : 0; }
};
struct PointTree {
vector<int> G[N];
#define eb emplace_back
inline void adde(int u, int v) { G[u].eb(v), G[v].eb(u); }
/* --------原树-------- */
int lca_dep[N], lca_tp[N], lca_fa[N], dfn[N], dfc, sz[N], son[N];
void lca_dfs1(int x, int fa) {
sz[x] = 1, son[x] = 0;
lca_fa[x] = fa;
for (auto y : G[x]) {
if (y == fa) continue;
lca_dfs1(y, x);
sz[x] += sz[y];
if (!son[x] || sz[y] > sz[son[x]]) son[x] = y;
}
}
void lca_dfs2(int x, int tp) {
lca_tp[x] = tp;
dfn[x] = ++dfc;
if (son[x]) lca_dep[son[x]] = lca_dep[x]+1, lca_dfs2(son[x], tp);
for (auto y : G[x]) {
if (dfn[y]) continue;
lca_dep[y] = lca_dep[x]+1;
lca_dfs2(y, y);
}
}
inline int lca(int x, int y) {
int tpx = lca_tp[x], tpy = lca_tp[y];
while (tpx != tpy) {
if (lca_dep[tpx] < lca_dep[tpy]) swap(tpx, tpy), swap(x, y);
x = lca_fa[tpx], tpx = lca_tp[x];
}
return lca_dep[x] < lca_dep[y] ? x : y;
}
inline int dis(int x, int y) {
return lca_dep[x] + lca_dep[y] - (lca_dep[lca(x, y)]<<1);
}
/* ---------重心--------- */
bool del[N];
int sos = 0, rt, rtsz; // size of subtree, root, root maxsize
void getsz(int x, int fa) {
sz[x] = 1;
int res = 0;
for (auto y : G[x]) {
if (y == fa || del[y]) continue;
getsz(y, x);
sz[x] += sz[y];
res = max(res, sz[y]);
}
res = max(res, sos-sz[x]);
if (res < rtsz) rtsz = res, rt = x;
}
inline void getrt(int x) {
rt = 0, rtsz = n+1, sos = sz[x];
getsz(x, 0);
getsz(rt, 0);
}
/* build point-devided tree */
int fa[N];
vector<int> p[N];
BetterSGT t1, t2;
void build(int x) {
del[x] = 1;
for (auto y : G[x])
if (!del[y]) {
getrt(y);
fa[rt] = x, build(rt);
}
}
inline int query(int x, int k) {
int res = 0;
res += t1.query(x, 0, k);
for (int i = x; fa[i]; i = fa[i]) {
int d = dis(x, fa[i]);
res += t1.query(fa[i], 0, k-d);
res -= t2.query(i, 0, k-d); // 删掉重统的部分
}
return res;
}
inline void change(int x, int y) {
t1.add(x, 0, y - a[x]);
for (int i = x; fa[i]; i = fa[i]) {
int d = dis(x, fa[i]);
t1.add(fa[i], d, y-a[x]);
t2.add(i, d, y-a[x]);
}
a[x] = y;
}
inline void prework() {
dfc = 0;
lca_dfs1(1, 0), lca_dfs2(1, 1);
getrt(1), fa[rt] = 0, build(rt);
rep(x, 1, n) {
int tmp = a[x];
a[x] = 0, change(x, tmp);
}
}
} ds;
signed main() {
FASTIO;
cin >> n >> m;
rep(i, 1, n) cin >> a[i];
rep(i, 2, n) {
int u, v;
cin >> u >> v;
ds.adde(u, v);
}
ds.prework();
int lastans = 0;
rep(i, 1, m) {
int op, x, y;
cin >> op >> x >> y;
x ^= lastans, y ^= lastans;
if (x > n) return 0;
if (op == 1 && y > n) return 0;
if (op == 0) cout << (lastans=ds.query(x, y)) << '\n';
else ds.change(x, y);
}
return 0;
}
例题 2 开店
一颗带权树,\(q\) 次询问,求所有点权在 \([l,r]\) 区间内的点到 \(x\) 的距离和,强制在线。
也是史。显然还是暴力跳根,但是用前缀和就可以统计了,最烦的是还是有离散化,复杂度仍然下不来。
时间复杂度 \(\mathcal O(n \log n+q \log^2 n)\),空间复杂度 \(\mathcal O(n \log n)\)
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define per(i, r, l) for (int i = r; i >= l; --i)
#define LL long long
#define ULL unsigned long long
#define PII pair<int,int>
#define PLL pair<LL,LL>
// #define int long long
#define FASTIO ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
const int N = 1.5e5+5,mod = 998244353;
int n, m, a[N];
template<typename Tp>
struct BIT {
vector<Tp> c;
vector<int> p;
inline int remap(int x) {
int l = 1, r = p.size()+1;
while (l < r) {
int mid = (l+r)>>1;
if (mid > p.size() || x <= p[mid-1]) r = mid;
else l = mid+1;
}
return l;
}
inline Tp query(int L, int R) {
L = remap(L), R = remap(R+1)-1;
return (L<=R?c[R]-c[L-1]:0);
}
inline void rebuild(vector<int>& ch, const vector<Tp>& d) {
p = ch; sort(p.begin(), p.end());
p.erase(unique(p.begin(), p.end()), p.end());
c.resize(p.size()+2, 0);
rep(i, 1, ch.size()) c[remap(ch[i-1])] += d[i-1];
rep(i, 1, p.size()) c[i] += c[i-1];
}
};
struct PointTree {
vector<PII> G[N];
#define eb emplace_back
inline void adde(int u, int v, int w) { G[u].eb(v, w), G[v].eb(u, w); }
/* --------原树-------- */
LL lca_dep[N];
int lca_tp[N], lca_fa[N], dfn[N], dfc, sz[N], son[N], sonedge[N];
void lca_dfs1(int x, int fa) {
sz[x] = 1, son[x] = 0;
lca_fa[x] = fa;
for (auto [y, z] : G[x]) {
if (y == fa) continue;
lca_dfs1(y, x);
sz[x] += sz[y];
if (!son[x] || sz[y] > sz[son[x]]) sonedge[x] = z, son[x] = y;
}
}
void lca_dfs2(int x, int tp) {
lca_tp[x] = tp;
dfn[x] = ++dfc;
if (son[x]) lca_dep[son[x]] = lca_dep[x]+sonedge[x], lca_dfs2(son[x], tp);
for (auto [y, z] : G[x]) {
if (dfn[y]) continue;
lca_dep[y] = lca_dep[x]+z;
lca_dfs2(y, y);
}
}
inline int lca(int x, int y) {
int tpx = lca_tp[x], tpy = lca_tp[y];
while (tpx != tpy) {
if (lca_dep[tpx] < lca_dep[tpy]) swap(tpx, tpy), swap(x, y);
x = lca_fa[tpx], tpx = lca_tp[x];
}
return lca_dep[x] < lca_dep[y] ? x : y;
}
inline LL dis(int x, int y) {
return lca_dep[x] + lca_dep[y] - (lca_dep[lca(x, y)]<<1);
}
/* ---------重心--------- */
bool del[N];
int sos = 0, rt, rtsz; // size of subtree, root, root maxsize
void getsz(int x, int fa) {
sz[x] = 1;
int res = 0;
for (auto [y, z] : G[x]) {
if (y == fa || del[y]) continue;
getsz(y, x);
sz[x] += sz[y];
res = max(res, sz[y]);
}
res = max(res, sos-sz[x]);
if (res < rtsz) rtsz = res, rt = x;
}
inline void getrt(int x) {
rt = 0, rtsz = n+1, sos = sz[x];
getsz(x, 0);
getsz(rt, 0);
}
/* build point-devided tree */
int fa[N];
BIT<LL> t1[N], t3[N];
BIT<int> t2[N], t4[N];
vector<int> ch[N], age[N];
vector<LL> d[N];
void build(int x) {
del[x] = 1;
age[x].emplace_back(a[x]), ch[x].emplace_back(x), d[x].emplace_back(0);
for (auto [y,z] : G[x])
if (!del[y]) {
getrt(y);
y = rt;
fa[y] = x, build(y);
d[y].clear();
for (auto it : ch[y]) d[y].push_back(dis(it, x));
t3[y].rebuild(age[y], d[y]), t4[y].rebuild(age[y], vector<int>(ch[y].size(), 1));
for (auto it : age[y]) age[x].push_back(it);
for (auto it : ch[y]) ch[x].push_back(it);
for (auto it : d[y]) d[x].push_back(it);
}
t1[x].rebuild(age[x], d[x]), t2[x].rebuild(age[x], vector<int>(ch[x].size(), 1));
}
inline LL query(int x, int L, int R) {
LL res = 0;
res += t1[x].query(L, R);
for (int i = x; fa[i]; i = fa[i]) {
LL d = dis(x, fa[i]);
res += t1[fa[i]].query(L, R)+t2[fa[i]].query(L,R)*d;
res -= t3[i].query(L, R)+t4[i].query(L, R)*d;
}
return res;
}
inline void prework() {
dfc = 0;
lca_dfs1(1, 0), lca_dfs2(1, 1);
getrt(1), fa[rt] = 0, build(rt);
}
} ds;
signed main() {
FASTIO;
int AGEMAX;
cin >> n >> m >> AGEMAX;
rep(i, 1, n) cin >> a[i];
rep(i, 2, n) {
int u, v, w;
cin >> u >> v >> w;
ds.adde(u, v, w);
}
ds.prework();
LL lastans = 0;
rep(i, 1, m) {
int u, a, b;
cin >> u >> a >> b;
a = (a+lastans)%AGEMAX, b = (b+lastans)%AGEMAX;
if (a > b) swap(a, b);
cout << (lastans=ds.query(u, a, b)) << '\n';
}
return 0;
}
常数写得好史啊。

浙公网安备 33010602011771号