#8. 「模板」树链剖分

题目传送门:#8. 「模板」树链剖分

前置知识

  • 重链:重链(Heavy Path)是指树链剖分中的一条主要的路径,该路径上的节点数量较多,相邻节点之间的距离较近。轻链(Light Path)是指树链剖分中的其他路径,相邻节点之间的距离较远。

  • LCA:最近公共祖先

分析

上树状数组

首先,我们需要定义一个数组vals来存储每个节点的权值。然后,我们可以使用一个数组tree来表示树状数组,数组的下标表示节点的编号。对于节点i,其在tree数组中的下标为i,其父节点在tree数组中的下标为i+(i&-i)

然后只需要实现以下几个操作:

换根:将树的根节点设置为新根节点x。我们可以将所有节点的权值都减去vals[x],然后再将vals[x]加到所有节点的权值中即可。

修改路径权值:给定两个节点u和v,将节点u到节点v的路径上的所有节点权值增加一个给定的值x。我们可以先将节点u的权值增加x,然后再将节点v的权值减去x。最后将tree数组中节点u到节点v的路径上的节点的值都增加x。

修改子树权值:给定一个节点x,将以节点x为根的子树内的所有节点权值增加一个给定的值y。我们可以将节点x的权值增加y,然后将tree数组中节点x及其所有子节点的值都增加y。

询问路径:给定两个节点u和v,询问节点u到节点v的路径上所有节点权值的和。我们可以先计算节点v到根节点的权值和,再计算节点u的权值,最后将两者相减即可。

询问子树:给定一个节点x,询问以节点x为根的子树内所有节点权值的和。我们可以直接返回tree数组中节点x及其所有子节点的值的和。

✿✿ヽ(°▽°)ノ✿

等等,还要上代码

Code

10分

cpp 复制代码
#include <cstdio>
#define reg register
int read() {
    reg int s = 0, f = 1;
    reg char ch;
    for (; (ch = getchar()) < '0' || ch > '9'; ch == '-' ? f = -f : 0)
        ;
    for (; ch >= '0' && ch <= '9'; s = (s << 3) + (s << 1) + ch - 48, ch = getchar())
        ;
    return s * f;
}
const int N = 1e5 + 50;
int next[N], to[N], first[N], tag[N << 2], tree[N << 2], val[N], cnt = 0, tot = 0, n, m;
int dfn[N], son[N], size[N], efn[N], dep[N], top[N], fa[N], root, g[N];
inline void push_up(int now) { tree[now] = tree[now << 1] + tree[now << 1 | 1]; }
inline void add(int now, int l, int r, int v) {
    tree[now] += (r - l + 1) * v;
    tag[now] += v;
}
void push_down(int now, int l, int r) {
    if (tag[now]) {
        int mid = (l + r) >> 1;
        add(now << 1, l, mid, tag[now]);
        add(now << 1 | 1, mid + 1, r, tag[now]);
        tag[now] = 0;
    }
}
void _add(int u, int v) { next[++cnt] = first[u], first[u] = cnt, to[cnt] = v; }
void build(int now, int l, int r) {
    if (l == r) {
        tree[now] = val[g[l]];
        return;
    }
    int mid = (l + r) >> 1;
    build(now << 1, l, mid);
    build(now << 1 | 1, mid + 1, r);
    push_up(now);
}
void mobify(int now, int l, int r, int L, int R, int v) {
    if (L <= l && r <= R) {
        add(now, l, r, v);
        return;
    }
    int mid = (l + r) >> 1;
    push_down(now, l, r);
    if (L <= mid)
        mobify(now << 1, l, mid, L, R, v);
    if (R > mid)
        mobify(now << 1 | 1, mid + 1, r, L, R, v);
    push_up(now);
}
int query(int now, int l, int r, int L, int R) {
    if (L <= l && r <= R)
        return tree[now];
    int mid = (l + r) >> 1, ans = 0;
    push_down(now, l, r);
    if (L <= mid)
        ans += query(now << 1, l, mid, L, R);
    if (R > mid)
        ans += query(now << 1 | 1, mid + 1, r, L, R);
    return ans;
}
void dfs0(int now) {
    son[now] = 0;
    size[now] = 1;
    for (reg int i = first[now]; i; i = next[i])
        if (!dep[to[i]]) {
            fa[to[i]] = now;
            dep[to[i]] = dep[now] + 1;
            dfs0(to[i]);
            size[now] += size[to[i]];
            if (!son[now] || size[to[i]] > size[son[now]])
                son[now] = to[i];
        }
}
void dfs1(int now, int t) {
    dfn[now] = ++tot;
    top[now] = t;
    g[tot] = now;
    if (son[now])
        dfs1(son[now], t);
    for (reg int i = first[now]; i; i = next[i])
        if (!dfn[to[i]])
            dfs1(to[i], to[i]);
    efn[now] = tot;
}
int lca(int u, int v) {
    while (top[u] != top[v]) (dep[top[u]] > dep[top[v]]) ? u = fa[top[u]] : v = fa[top[v]];
    return (dep[u] < dep[v]) ? u : v;
}
void addpath(int u, int v, int k) {
    while (top[u] != top[v])
        (dep[top[u]] > dep[top[v]]) ? (mobify(1, 1, n, dfn[top[u]], dfn[u], k), u = fa[top[u]])
                                    : (mobify(1, 1, n, dfn[top[v]], dfn[v], k), v = fa[top[v]]);
    (dep[u] < dep[v]) ? mobify(1, 1, n, dfn[u], dfn[v], k) : mobify(1, 1, n, dfn[v], dfn[u], k);
}
void addtree(int u, int k) {
    if (u == root) {
        mobify(1, 1, n, 1, n, k);
        return;
    }
    int g = lca(u, root);
    if (g != u)
        mobify(1, 1, n, dfn[u], efn[u], k);  // puts("wtf");
    else {
        // puts("wtm");
        if (dfn[root] > 1)
            mobify(1, 1, n, 1, dfn[root] - 1, k);
        if (efn[root] < n)
            mobify(1, 1, n, efn[root] + 1, n, k);
        // mobify(1,1,n,1,dfn[u]-1,k);
        // mobify(1,1,n,efn[u]+1,n,k);
    }
}
void querypath(int u, int v) {
    int ans = 0;
    while (top[u] != top[v])
        if (dep[top[u]] > dep[top[v]])
            ans += query(1, 1, n, dfn[top[u]], dfn[u]), u = fa[top[u]];
        else
            ans += query(1, 1, n, dfn[top[v]], dfn[v]), v = fa[top[v]];
    // printf("%d %d %d\n",u,v,ans);
    ans += (dep[u] < dep[v]) ? query(1, 1, n, dfn[u], dfn[v]) : query(1, 1, n, dfn[v], dfn[u]);
    printf("%d\n", ans);
}
void querytree(int u) {
    if (u == root) {
        printf("%d\n", tree[1]);
        return;
    }
    int g = lca(u, root);
    if (g != u)
        printf("%d\n", query(1, 1, n, dfn[u], efn[u]));
    else {
        int ans = 0;
        if (dfn[root] > 1)
            ans += query(1, 1, n, 1, dfn[root] - 1);
        if (efn[root] < n)
            ans += query(1, 1, n, efn[root] + 1, n);
        printf("%d\n", ans);
    }
}
int main() {
    n = read();
    root = 1;
    for (reg int i = 1; i <= n; i++) val[i] = read();
    for (reg int u = 2, v; u <= n; u++) v = read(), _add(v, u);
    m = read();
    dep[1] = 1;
    dfs0(1);
    dfs1(1, 1);
    build(1, 1, n);
    for (reg int i = 1, opt, u, v, k; i <= m; i++) {
        opt = read();
        if (opt == 1)
            root = read();
        if (opt == 2)
            u = read(), v = read(), k = read(), addpath(u, v, k);
        if (opt == 3)
            u = read(), k = read(), addtree(u, k);
        if (opt == 4)
            u = read(), v = read(), querypath(u, v);
        if (opt == 5)
            u = read(), querytree(u);
    }
    return 0;
}

100分代码

cpp 复制代码
#include <bits/stdc++.h>
#define INF 0x7fffffff
#define ll long long
using namespace std;

ll read() {
    ll k = 1, x = 0;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            k = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - 48, ch = getchar();
    return k * x;
}

const int N = 3e5 + 10;
struct EDGE {
    ll to, pre;
} edge[N];
ll a[N], size[N], fa[N], dep[N], id[N], w[N], son[N], top[N], head[N];
ll cnt, n, m, r;

//========== 关于换根的操作 =========
inline ll find(ll u, ll v) {  //找从u(题目查询点的)到root第一个儿子
    while (top[u] != top[v]) {
        if (dep[top[u]] > dep[top[v]])
            swap(u, v);  //保证u的深度更浅
        if (fa[top[v]] == u)
            return top[v];  //若链头的父亲恰好为u返回链头
        v = fa[top[v]];     //不断向上跳
    }
    if (dep[u] > dep[v])
        swap(u, v);
    return son[u];
}
inline ll lca(ll u, ll v) {
    while (top[u] != top[v]) {
        if (dep[top[u]] > dep[top[v]])
            swap(u, v);
        v = fa[top[v]];
    }
    return dep[u] >= dep[v] ? v : u;
}
//==================================

//================== 以下为线段树模板 ====================
#define up(x) t[x].sum = t[x << 1].sum + t[x << 1 | 1].sum
struct node {
    ll l, r;
    ll sum, lazy;
} t[N << 2];
ll len(ll p) { return t[p].r - t[p].l + 1; }
void brush(ll p, ll k) {
    t[p].lazy += k;
    t[p].sum += len(p) * k;
}
void push_down(ll p) {
    brush(p << 1, t[p].lazy);
    brush(p << 1 | 1, t[p].lazy);
    t[p].lazy = 0;
}
void build(ll p, ll l, ll r) {
    t[p].l = l;
    t[p].r = r;
    if (l == r) {
        t[p].sum = w[l];
        return;
    }
    ll mid = l + r >> 1;
    build(p << 1, l, mid);
    build(p << 1 | 1, mid + 1, r);
    up(p);
}
void update(ll p, ll l, ll r, ll k) {
    if (t[p].l >= l && t[p].r <= r) {
        brush(p, k);
        return;
    }
    push_down(p);
    ll mid = t[p].l + t[p].r >> 1;
    if (mid >= l)
        update(p << 1, l, r, k);
    if (r >= mid + 1)
        update(p << 1 | 1, l, r, k);
    up(p);
}
ll getans(ll p, ll l, ll r) {
    if (t[p].l >= l && t[p].r <= r)
        return t[p].sum;
    push_down(p);
    ll ans = 0;
    ll mid = t[p].l + t[p].r >> 1;
    if (l <= mid)
        ans += getans(p << 1, l, r);
    if (r >= mid + 1)
        ans += getans(p << 1 | 1, l, r);
    return ans;
}
//================== 以上为线段树模板 ====================

//================== 以下为树剖操作模板 ====================
inline void updatetree(ll s, ll t, ll c) {  // s到t的简单路径加上c
    while (top[s] != top[t]) {              //倍增思想,深度大的往上跳
        if (dep[top[s]] > dep[top[t]])
            swap(s, t);                   //默认t深度大
        update(1, id[top[t]], id[t], c);  //先维护这一段链
        t = fa[top[t]];                   //跳到这个链头的父节点,维护下一个链
    }
    if (dep[s] > dep[t])
        swap(s, t);              //默认t深度大
    update(1, id[s], id[t], c);  //维护s与现在t(同深度)的链
}
inline ll getanstree(ll s, ll t) {  //查询s到t的简单路径权值和
    ll ans = 0;
    while (top[s] != top[t]) {  //倍增思想,深度大的往上跳
        if (dep[top[s]] > dep[top[t]])
            swap(s, t);                       //默认t深度大
        ans += getans(1, id[top[t]], id[t]);  //先查询这一段链
        t = fa[top[t]];                       //跳到这个链头的父节点,查询下一个链
    }
    if (dep[s] > dep[t])
        swap(s, t);                  //默认t深度大
    ans += getans(1, id[s], id[t]);  //查询s与现在t(同深度)的链
    return ans;
}
inline void updateson(ll x, ll t) {  //以x为根的子树加上t
    if (x == r)
        update(1, 1, n, t);  //当 x=root时,x就是此时整棵树的根,那么就是全局修改。
    else {
        ll LCA = lca(x, r);
        if (LCA != x)  //若root不在x的子树中,正常修改
            update(1, id[x], id[x] + size[x] - 1, t);
        else {  //若在子树内,修改它到根的第一个儿子子树的补集
            ll u = find(x, r);
            update(1, 1, n, t);
            update(1, id[u], id[u] + size[u] - 1, -t);
        }
    }
}
inline ll getansson(ll x) {  //以x为根的子树加上t
    if (x == r)
        return getans(1, 1, n);  //当 x=root时,x就是此时整棵树的根,那么就是全局查询。
    else {
        ll LCA = lca(x, r);
        if (LCA != x)  //若root不在x的子树中,正常查询
            return getans(1, id[x], id[x] + size[x] - 1);
        else {  //若在子树内,查询它到根的第一个儿子子树的补集
            ll u = find(x, r);
            return getans(1, 1, n) - getans(1, id[u], id[u] + size[u] - 1);
        }
    }
}
//================== 以上为树剖操作模板 ====================

//================== 以下为dfs ====================
inline void dfs1(ll x, ll fath, ll deep) {  // x当前节点,f父亲,deep深度
    dep[x] = deep;                          //标记每个点的深度
    fa[x] = fath;                           //标记每个点的父亲
    size[x] = 1;                            //标记每个非叶子节点的子树大小
    ll maxson = -1;                         //记录重儿子的儿子数
    for (ll i = head[x]; i; i = edge[i].pre) {
        ll y = edge[i].to;
        if (y == fath)
            continue;
        dfs1(y, x, deep + 1);
        size[x] += size[y];      //把它的儿子数加到它身上
        if (size[y] > maxson) {  //标记每个非叶子节点的重儿子编号
            son[x] = y;
            maxson = size[y];
        }
    }
}

ll tim;
inline void dfs2(ll x, ll fst) {  // x当前节点,fst当前链的顶端
    id[x] = ++tim;                //标记每个点dfs序
    w[tim] = a[x];                //赋每个点的初始值
    top[x] = fst;                 //标记这个点所在链的顶端
    if (!son[x])
        return;         //如果没有儿子则返回
    dfs2(son[x], fst);  //按先处理重儿子
    for (ll i = head[x]; i; i = edge[i].pre) {
        ll y = edge[i].to;
        if (y == fa[x] || y == son[x])
            continue;
        dfs2(y, y);  //轻儿子都有一条从它自己开始的链
    }
}
//================== 以上为dfs ====================

inline void add(ll u, ll v) {  //链式前向星
    edge[++cnt].pre = head[u];
    edge[cnt].to = v;
    head[u] = cnt;
}

int main() {
    n = read();
    r = 1;
    for (ll i = 1; i <= n; i++) a[i] = read();
    for (ll i = 1; i < n; i++) {
        ll a;
        a = read();
        add(a, i + 1), add(i + 1, a);
    }

    dfs1(r, 0, 1);
    dfs2(r, r);
    build(1, 1, n);
    m = read();
    for (ll i = 1; i <= m; i++) {
        ll opt = read(), x = read();
        if (opt == 1)
            r = x;
        if (opt == 2) {
            ll y = read(), z = read();
            updatetree(x, y, z);
        }
        if (opt == 4) {
            ll y = read();
            cout << getanstree(x, y) << endl;
        }
        if (opt == 3) {
            ll z = read();
            updateson(x, z);
        }
        if (opt == 5)
            cout << getansson(x) << endl;
    }
    return 0;
}
相关推荐
rgw20104 个月前
P10789 [NOI2024] 登山
数据结构·动态规划·树上倍增·主席树·树链剖分·前缀优化