loading...

点分树

学名:\([\text{C}_6\text{H}_{10}\text{O}_5]_n\) tree

介绍

点分治的思路很简单,每次选重心,统计答案并分治。

点分树就是多维护了点分治的一个信息:每一层分治的重心记录它的上一个重心。

按这个关系建树,于是得到一个与原树形态“无关的”的新树。

这颗新树有如此的性质:

  1. 树上节点的深度 \(dep_i \le \lceil\log_2 n\rceil\)。即对树分层,总共只会有 \(\log n\) 层。
  2. 两个节点在新树上的 \(\mathrm{lca}\) 是原数上这两个节点路径上的某个点。

根据 1 性质,就发现点分树可以做各种一般树做不到的操作:

  1. 暴力跳到根,时间复杂度 \(\mathcal O(\log n)\)
  2. 每个节点存储它子树中所有节点的信息,时空复杂度 \(\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;
}

常数写得好史啊。

posted @ 2025-04-12 11:57  goldspade  阅读(24)  评论(0)    收藏  举报