#8. 「模板」樹鏈剖分

AnRain安林發表於2024-08-26

題目傳送門:#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分

#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分程式碼

#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;
}

相關文章