📜  欧拉之旅|使用细分树的子树总和(1)

📅  最后修改于: 2023-12-03 14:55:53.526000             🧑  作者: Mango

欧拉之旅 | 使用细分树的子树总和

简介

欧拉之旅是一种基于欧拉回路和欧拉序列的树上技巧,常常被用于树上统计问题的解决。而使用细分树的子树总和,则是欧拉之旅的一种变体实现,它能够高效地处理子树和的统计问题。

操作原理

细分树其实就是一棵带权树状数组,每个节点记录当前子树内的所有节点的权值和。通过先进行一次欧拉遍历,获取每个节点的入栈和出栈时间戳,并在细分树上维护这两个值之间的区间,即可快速地计算出任意一个节点的子树和。

代码实现

下面是使用C++实现的细分树的子树总和计算方法:

vector<int> head, nxt, to, w;
int idx;
inline void add(int u, int v, int val) {
    to[++idx] = v, w[idx] = val;
    nxt[idx] = head[u];
    head[u] = idx;
}
int dep[N], siz[N], fa[N], son[N], top[N], dfn[N], ts;
void dfs1(int x) {
    siz[x] = 1;
    for (int i = head[x]; i; i = nxt[i]) {
        int y = to[i];
        if (y == fa[x]) continue;
        dep[y] = dep[x] + 1, fa[y] = x;
        dfs1(y), siz[x] += siz[y];
        if (siz[y] > siz[son[x]]) son[x] = y;
    }
}
void dfs2(int x, int tp) {
    top[x] = tp, dfn[x] = ++ts;
    if (son[x]) dfs2(son[x], tp);
    for (int i = head[x], y; i; i = nxt[i]) {
        y = to[i];
        if (y == fa[x] || y == son[x]) continue;
        dfs2(y, y);
    }
}
struct SegTree {
    int l, r;
    ll s, tag;
    #define getmid int mid = (l + r) >> 1, ls = o << 1, rs = o << 1 | 1
    #define lsq ls, l, mid
    #define rsq rs, mid+1, r
    #define pushup s = tr[ls].s + tr[rs].s
    #define pushdown tr[ls].tag += tr[o].tag, tr[ls].s += (mid-l+1) * tr[o].tag, tr[rs].tag += tr[o].tag, tr[rs].s += (r-mid) * tr[o].tag, tr[o].tag = 0;
    #define chk if (!o) o = ++tott, tr[o].l = l, tr[o].r = r
}sgt[N << 2]; int tott;
void modify(int o, int l, int r, int ql, int qr, int val) {
    if (ql <= l && r <= qr) {
        sgt[o].s += (r-l+1) * val, sgt[o].tag += val;
        return;
    }
    getmid, chk;
    if (ql <= mid) modify(lsq, ql, qr, val);
    if (mid < qr) modify(rsq, ql, qr, val);
    pushup;
}
ll query(int o, int l, int r, int ql, int qr) {
    if (ql <= l && r <= qr) return sgt[o].s;
    getmid, chk, pushdown;
    ll res = 0;
    if (ql <= mid) res += query(lsq, ql, qr);
    if (mid < qr) res += query(rsq, ql, qr);
    return res;
}
ll query_path(int x, int y) {
    ll res = 0;
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) swap(x, y);
        res += query(1, 1, n, dfn[top[x]], dfn[x]);
        x = fa[top[x]];
    }
    if (dep[x] > dep[y]) swap(x, y);
    res += query(1, 1, n, dfn[x], dfn[y]);
    return res;
}
void modify_subtree(int x, int val) {
    modify(1, 1, n, dfn[x], dfn[x] + siz[x] - 1, val);
}
操作说明

使用上述代码,我们可以用modify_subtree(x, val)函数修改节点x及其子树的权值,用query_path(x, y)函数查询节点x到y路径上的所有节点的权值和。其中x与y分别为要查询的起点和终点。

总结

细分树的子树总和是欧拉之旅的一种变体使用方法,它可以高效地处理子树和的统计问题。使用细分树的代码实现并不复杂,但需要理解其中细节并仔细思考。