Loading

[模板] 点分树

前言

确实是树上问题中一个非常神奇的部分
好好学一下

思路

点分树的性质其实还是比较好理解, 这里重点解释点分树的两棵段树是怎么回事, 具体如何维护

首先要知道的是, 维护两棵段树的目的是为了去除同子树中的贡献

贡献计算(查询)
  • fai\textrm{fa}_i 表示点分树上的父子关系
  • subtree(i)\textrm{subtree}(i) 表示点 ii 在点分树上的子树集合
  • fatree(i)\textrm{fatree}(i) 表示点 ii 在点分树上的父树集合
  • dis(i,j)\textrm{dis}(i, j) 表示点 ii 和点 jj 在点分树上的距离
  • wiw_i 表示点 ii 的点权
    f(i,j)=xsubtree(i),dis(x,i)jwxg(i,j)=xsubtree(i),dis(x,fai)jwx\begin{align} \textrm{f}(i, j) = \sum_{x \in \textrm{subtree}(i),\textrm{dis}(x,i) \leq j} w_x \\ \textrm{g}(i, j) = \sum_{x \in \textrm{subtree}(i),\textrm{dis}(x,\textrm{fa}_i) \leq j} w_x \end{align}

其中

  • (1)(1) 式表示重构树上 ii 的子树中与 ii 距离小于等于 jj 的点权值之和
  • (2)(2) 式表示重构树上 ii 的子树中与 fai\textrm{fa}_i 距离小于等于 jj 的点权值之和

如何利用?

假设当前查询 {x,k}\{x, k\} , 即查询所有距离 xx 小于等于 kk 的点权和
对于重构树上的父子节点 (i,fai)(i, \textrm{fa}_i) , subtree(fai)subtree(i)\textrm{subtree}(\textrm{fa}_i) - \textrm{subtree}(i) ((即除了 ii 子树外面的部分)) 对答案的贡献为
con(i,fai)=f(fai,kdis(x,fai))g(i,kdis(x,fai))\textrm{con}(i, \textrm{fa}_i) = \textrm{f} \Big(\textrm{fa}_i, k - \textrm{dis}(x, \textrm{fa}_i)\Big) - \textrm{g} \Big(i, k - \textrm{dis}(x, \textrm{fa}_i)\Big)

解释一下, 我们考虑只在重构树上的 LCA(x,v)\textrm{LCA}(x, v) 处统计 vv 的贡献((一个性质是 重构树上的 LCA(x,v)\textrm{LCA}(x, v) 一定是 xvx \to v 的必经之路)), 也就是说如果枚举 u=LCA(x,v)u = \textrm{LCA}(x, v) , 那么满足条件的 vv 必定满足 dis(u,v)kdis(x,u)\textrm{dis}(u, v) \leq k - \textrm{dis}(x, u)
不难发现 v,xsubtree(u)v, x \in \textrm{subtree}(u) , 考虑枚举 ufatree(x)u \in \textrm{fatree}(x) , 借以统计答案
上面计算的 con(i,fai)\textrm{con}(i, \textrm{fa}_i) 的意义就是枚举 u=faiu = \textrm{fa}_i , dis(u,v)kdis(x,u)\textrm{dis}(u, v) \leq k - \textrm{dis}(x, u) 的贡献

但是还是有问题, 不难发现如果 x,vx, vuu 的同一个子树之中, 那么其 LCA\rm{LCA} 必然不等于 uu , 那么 con(i,fai)\textrm{con}(i, \textrm{fa}_i) 的贡献就会重复, 所以需要用以下操作去重

  • f(fai,kdis(x,fai))\textrm{f} \Big(\textrm{fa}_i, k - \textrm{dis}(x, \textrm{fa}_i)\Big) :
    表示重构树上点 ii 的子树集合中, 距离 ii 小于等于 kdis(x,fai)k - \textrm{dis}(x, \textrm{fa}_i) 的点权和
  • g(i,kdis(x,fai))\textrm{g} \Big(i, k - \textrm{dis}(x, \textrm{fa}_i)\Big) :
    发现上一个式子包含了两点同在一个子树内的情况, 所以需要减去重构树上点 ii 的子树中, 距离 fai\textrm{fa}_i 小于等于 kdis(x,fai)k - \textrm{dis}(x, \textrm{fa}_i) 的点权和

最终的答案就是
ans=f(x,k)+ifatree(x),faicon(i,fai)ans = \textrm{f}(x, k) + \sum_{i \in \textrm{fatree}(x), \exists \textrm{fa}_i} \textrm{con}(i, \textrm{fa}_i)

贡献更新(修改)

考虑修改的情况, 其实跟上面很像, 按照定义修改 f\textrm{f}g\textrm{g}
具体怎么做?

对于这个点你往上暴力跳 ii , 然后更新即可

所以我们现在需要维护单点修改, 询问前缀, 不难发现可以用动态开点线段树处理
用树链剖分维护 \(\rm{LCA}\) 即可

代码
#include <bits/stdc++.h>
const int MAXN = 1e5 + 20;

int n, m;
int w[MAXN];

/*动态开点线段树*/
struct DSTree {
    struct node { int ls, rs; int val; node() { ls = rs = val = 0; } }; std::vector<node> Tree;
    int cnt, rt;
    DSTree() { cnt = 0, rt = 0; }
    /*----------------------------------------*/
    void pushup(int rt) { Tree[rt].val = Tree[Tree[rt].ls].val + Tree[Tree[rt].rs].val; }
    // void pushdown()
    // void addtag()
    /*----------------------------------------*/
    void build(int sz) { Tree.resize((sz + 1) * 6); }
    void update(int& rt, int l, int r, int val, int aim) {
        if (!rt) rt = ++cnt;
        if (l == r) { Tree[rt].val += val; return; }
        int mid = (l + r) >> 1;
        if (aim <= mid) update(Tree[rt].ls, l, mid, val, aim);
        else update(Tree[rt].rs, mid + 1, r, val, aim);
        pushup(rt);
    }
    int query(int rt, int l, int r, int L, int R) {
        if (!rt) return 0;
        if (L <= l && r <= R) return Tree[rt].val;
        int mid = (l + r) >> 1, ans = 0;
        if (L <= mid) ans += query(Tree[rt].ls, l, mid, L, R);
        if (R > mid) ans += query(Tree[rt].rs, mid + 1, r, L, R);
        return ans;
    }
};

/*树*/
struct graph {
    struct node { int to, nxt; } edge[MAXN << 1]; int head[MAXN], cnt = -1;
    void head_init() { memset(head, -1, sizeof head); }
    void addedge(int u, int v) { edge[++cnt] = (node) { v, head[u] }; head[u] = cnt; }

    int tmp; // dfn 辅助
    graph() { tmp = 0, head_init(); }

    struct point {
        DSTree f, g;
        int dep, fa, siz, Hson, dfn, top;
        int mxt;
    } dot[MAXN];
} tree, retr;

/*树链剖分*/
class TreeDivder {
private:
    void dfs1(int u, int fat) {
        tree.dot[u].dep = tree.dot[fat].dep + 1, tree.dot[u].fa = fat, tree.dot[u].siz = 1;
        for (int e = tree.head[u]; ~e; e = tree.edge[e].nxt) {
            int v = tree.edge[e].to; if (v == fat) continue;
            dfs1(v, u); tree.dot[u].siz += tree.dot[v].siz;
            if (!tree.dot[u].Hson || tree.dot[v].siz > tree.dot[tree.dot[u].Hson].siz) tree.dot[u].Hson = v;
        }
    }
    void dfs2(int u, int topu) {
        tree.dot[u].dfn = ++tree.tmp, tree.dot[u].top = topu;
        if (tree.dot[u].Hson) dfs2(tree.dot[u].Hson, topu);
        for (int e = tree.head[u]; ~e; e = tree.edge[e].nxt) {
            int v = tree.edge[e].to; if (v == tree.dot[u].fa || v == tree.dot[u].Hson) continue;
            dfs2(v, v);
        }
    }

public:
    void init(int rt) { tree.dot[rt].dep = 1; dfs1(rt, 0); dfs2(rt, rt); }
    int LCA(int u, int v) {
        while (tree.dot[u].top != tree.dot[v].top) {
            /*让链头更深的先跳*/ if (tree.dot[tree.dot[u].top].dep < tree.dot[tree.dot[v].top].dep) std::swap(u, v);
            u = tree.dot[tree.dot[u].top].fa;
        }
        return (tree.dot[u].dep < tree.dot[v].dep) ? u : v;
    }
    int dis(int u, int v) { return tree.dot[u].dep + tree.dot[v].dep - 2 * tree.dot[LCA(u, v)].dep; }
} diver;

/*重构树*/
class TreeRestructer {
private:
    int done[MAXN], sum; // 已经处理完的部分

public:
    TreeRestructer(int x) { sum = x; memset(done, false, sizeof done); }
    /*获取该连通块的重心*/
    void getrt(int u, int fat, int& rt) {
        tree.dot[u].siz = 1, tree.dot[u].mxt = 0;
        for (int e = tree.head[u]; ~e; e = tree.edge[e].nxt) {
            int v = tree.edge[e].to; if (v == fat || done[v]) continue;
            getrt(v, u, rt); tree.dot[u].siz += tree.dot[v].siz;
            tree.dot[u].mxt = std::max(tree.dot[u].mxt, tree.dot[v].siz);
        }
        tree.dot[u].mxt = std::max(tree.dot[u].mxt, sum - tree.dot[u].siz);
        if (tree.dot[u].mxt <= tree.dot[rt].mxt || !rt) rt = u;
    }
    /*建立重构树*/
    void build(int rt, int fat) {
        done[rt] = true; int subsize = sum;
        retr.dot[rt].fa = fat;
        retr.dot[rt].f.build(subsize / 2 + 1), retr.dot[rt].g.build(subsize + 1);
        retr.dot[rt].siz = subsize;
        for (int e = tree.head[rt]; ~e; e = tree.edge[e].nxt) {
            int v = tree.edge[e].to; if (done[v]) continue;
            sum = tree.dot[v].siz > tree.dot[rt].siz ? subsize - tree.dot[rt].siz : tree.dot[v].siz;
            int x; tree.dot[x = 0].mxt = 0x3f3f3f3f;
            getrt(v, rt, x); build(x, rt);
        }
    }
};

/*处理问题*/
class AnsGiver {
private:
    void modify(int idx, int val) {
        int now = idx;
        while (~now) {
            int fa = retr.dot[now].fa;
            retr.dot[now].f.update(retr.dot[now].f.rt, 0, retr.dot[now].siz, val, diver.dis(now, idx));
            if (~fa) retr.dot[now].g.update(retr.dot[now].g.rt, 0, retr.dot[fa].siz, val, diver.dis(fa, idx));
            now = fa;
        }
    }
    int query(int idx, int k) {
        int res = 0;
        int now = idx, last = 0;
        while (~now) {
            int d = diver.dis(idx, now);
            if (d > k) { last = now, now = retr.dot[now].fa; continue; } // 注意是 continue
            res += retr.dot[now].f.query(retr.dot[now].f.rt, 0, retr.dot[now].siz, 0, k - d);
            if (last) res -= retr.dot[last].g.query(retr.dot[last].g.rt, 0, retr.dot[now].siz, 0, k - d);
            last = now, now = retr.dot[now].fa;
        }
        return res;
    }

public:
    int lastans = 0;
    AnsGiver() { lastans = 0; }
    void decode(int& x, int& y) { x ^= lastans, y ^= lastans; }
    void solve() {
        for (int i = 1; i <= n; i++) modify(i, w[i]);
        for (int i = 1, op, x, y; i <= m; i++) {
            scanf("%d %d %d", &op, &x, &y); decode(x, y);
            if (op) { modify(x, y - w[x]); w[x] = y; }
            else { printf("%d\n", lastans = query(x, y)); }
        }
    }
} ag;

int main()
{
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
    tree.head_init(); for (int i = 1, u, v; i < n; i++) scanf("%d %d", &u, &v), tree.addedge(u, v), tree.addedge(v, u);

    /*先处理树链剖分*/ diver.init(1);
    /*建立重构树*/ TreeRestructer tr(n); int rt; tree.dot[rt = 0].mxt = 0x3f3f3f3f; tr.getrt(1, -1, rt), tr.build(rt, -1);
    ag.solve();

    return 0;
}

不知道 \(\rm{MLE}\) 是什么造成的, 后面再看

posted @ 2025-02-19 21:16  Yorg  阅读(11)  评论(0)    收藏  举报