OI笔记 | 重链剖分笔记

重链剖分

模板题:洛谷 P3384

重链剖分可以把树分割成若干条链,且保证划分出的每条链上的节点 dfs 序连续,从而可以用维护序列的数据结构(如线段树)来维护树的路径上的信息。

我们定义 $hson(x)$ 为 $x$ 的重儿子,即 $x$ 的所有儿子中,子树大小最大的。

从一个节点到它的重子节点的边称为重边。若干条首尾衔接的重边构成重链

只需要用两次简单的 dfs 就可以实现树剖。

第一次 dfs 中,我们需要求出每个节点的 重儿子 $hson(x)$、深度 $dep(x)$、以它为根的子树大小 $siz(x)$、父亲 $fa(x)$。

第二次 dfs 中,需要 优先 dfs 每个节点的重儿子,这可以让同一条重链上的点的 dfs 序连续。我们需要求出每个节点的 dfs序 $dfn(x)$、所在重链的深度最小的节点 $top(x)$。顺带着把点权赋值到序列上,即 $a_{dfn(x)}\gets v_x$。

到这一步树剖其实已经完成,已经可以用这些信息求 $\operatorname{LCA}(x,y)$ 了。实现就是将 $top$ 深度较大的节点往上跳到 $fa(top(x))$,直到两个节点在同一条重链上。代码如下:

int lca(int x, int y) {
    while(top[x] != top[y]) {
        if(dep[top[x]] > dep[top[y]])   x = fa[top[x]];
        else    y = fa[top[y]];
    }
    return dep[x] < dep[y] ? x : y;
}

考虑模板题中的信息维护,我们用一棵线段树来维护权值和。

先看操作 3 和 4,比较简单:

由于子树内的 $dfn$ 也是连续的,所以对于子树内的修改与查询,我们只需要直接对区间 $[dfn(x),dfn(x)+siz(x)-1]$ 进行操作即可。

再看操作 1 和 2:

我们像 LCA 一样,每次让将 $top$ 深度较大的节点往上跳到 $fa(top(x))$,直到两个节点在同一条重链上。

每次跳的时候都 修改/查询 区间 $[dfn(top(x)),dfn(x)]$($dep(top(x))>dep(top(y))$)。

最后在同一条重链上时再 修改/查询 区间 $[dfn(x), dfn(y)]$($dep(x)<dep(y)$)。

注意这题每一步都要取模,我有一个地方没加取模然后调了半个小时。而且把数据下载下来调居然在 dfs 时就爆栈了,加了编译选项才正常。

参考代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 1e5 + 10;
int n, m, rt, MOD, dfncnt;
int v[MAX_N], top[MAX_N], hson[MAX_N], dep[MAX_N], fa[MAX_N], siz[MAX_N], dfn[MAX_N];
int a[MAX_N], d[MAX_N << 2], b[MAX_N << 2];
vector<int> G[MAX_N];

inline ll read() {...}
inline void write(ll x) {...}
void dfs1(int u, int pre) {
    hson[u] = -1;
    siz[u] = 1;
    for(int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        if(v == pre) continue;
        dep[v] = dep[u] + 1;
        fa[v] = u;
        dfs1(v, u);
        siz[u] += siz[v];
        if(hson[u] == -1 || siz[v] > siz[hson[u]])  hson[u] = v;
    }
}
void dfs2(int u, int tp) {
    top[u] = tp;
    dfn[u] = ++dfncnt;
    a[dfncnt] = v[u];
    if(hson[u] == -1)   return;
    dfs2(hson[u], tp);
    for(int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        if(v != fa[u] && v != hson[u])  dfs2(v, v);
    }
}

inline int lc(int p) {return (p << 1);}
inline int rc(int p) {return (p << 1) | 1;}
inline int mid(int s, int t) {return (s + ((t - s) >> 1));}
inline void pu(int p) {d[p] = (d[lc(p)] + d[rc(p)]) % MOD;}
inline void pd(int p, int s, int t) {
    if(b[p]) {
        int m = mid(s, t);
        (d[lc(p)] += (m - s + 1) * b[p]) %= MOD;
        (d[rc(p)] += (t - m) * b[p]) %= MOD;
        (b[lc(p)] += b[p]) %= MOD;
        (b[rc(p)] += b[p]) %= MOD;
        b[p] = 0;
    }
}
void build_tree(int s, int t, int p) {
    if(s == t) {d[p] = a[s]; return;}
    int m = mid(s, t);
    build_tree(s, m, lc(p));
    build_tree(m + 1, t, rc(p));
    pu(p);
}
void update(int s, int t, int p, int l, int r, int k) {
    if(l <= s && t <= r) {
        (d[p] += (t - s + 1) * k) %= MOD;
        (b[p] += k) %= MOD;
        return;
    }
    pd(p, s, t);
    int m = mid(s, t);
    if(l <= m)  update(s, m, lc(p), l, r, k);
    if(r > m)   update(m + 1, t, rc(p), l, r, k);
    pu(p);
}
int query(int s, int t, int p, int l, int r) {
    if(l <= s && t <= r)    return d[p];
    pd(p, s, t);
    int m = mid(s, t), ret = 0;
    if(l <= m)  (ret += query(s, m, lc(p), l, r)) %= MOD;
    if(r > m)   (ret += query(m + 1, t, rc(p), l, r)) %= MOD;
    return ret;
}
void update_path(int x, int y, int k) {
    while(top[x] != top[y]) {
        if(dep[top[x]] < dep[top[y]])   swap(x, y);
        update(1, n, 1, dfn[top[x]], dfn[x], k);
        x = fa[top[x]];   
    }
    if(dep[x] > dep[y]) swap(x, y);
    update(1, n, 1, dfn[x], dfn[y], k);
}
int query_path(int x, int y) {
    int ret = 0;
    while(top[x] != top[y]) {
        if(dep[top[x]] < dep[top[y]])   swap(x, y);
        (ret += query(1, n, 1, dfn[top[x]], dfn[x])) %= MOD;
        x = fa[top[x]];
    }
    if(dep[x] > dep[y]) swap(x, y);
    (ret += query(1, n, 1, dfn[x], dfn[y])) %= MOD;
    return ret;
}
int main() {
    n = read(), m = read(), rt = read(), MOD = read();
    for(int i = 1; i <= n; i++) v[i] = read();
    for(int i = 1; i <= n - 1; i++) {
        int u = read(), v = read();
        G[u].push_back(v), G[v].push_back(u);
    }
    dfs1(rt, 0);
    dfs2(rt, rt);
    build_tree(1, n, 1);
    while(m--) {
        int op = read();
        if(op == 1) {
            int x = read(), y = read(), k = read();
            update_path(x, y, k);
        }
        else if(op == 2) {
            int x = read(), y = read();
            write(query_path(x, y));
            putchar('\n');
        }
        else if(op == 3) {
            int x = read(), k = read();
            update(1, n, 1, dfn[x], dfn[x] + siz[x] - 1, k);
        }
        else {
            int x = read();
            write(query(1, n, 1, dfn[x], dfn[x] + siz[x] - 1));
            putchar('\n');
        }
    }
    return 0;
}

换根

模板题:LOJ 139

如果有换根的操作,会影响我们对子树的修改/查询。我们不可能换一次根树剖一次。事实上,换根后修改/查询以 $x$ 为根的子树,有三种情况:

  1. 当 $x=root$ 时,全局修改/查询。

  2. 当 $x$ 不在 $1$ 到 $root$ 的链上时,等价于修改/查询原来的子树。

  3. 当 $x$ 在 $1$ 到 $root$ 的链上时,等价于修改/查询 【$x$ 在 $root$ 方向的子树】 的补集。

想要判断出第 $3$ 种情况,首先要满足 $dep_{root} > dep_x$,其次我们可以倍增找到 $x$ 的第 $(dep_{root} - dep_x - 1)$ 倍祖先 $y$,如果 $y$ 的父亲恰是 $x$,则显然 $x$ 在 $1$ 到 $root$ 的链上。

第 $2$ 种情况对应的 dfs 序区间为 $[dfn_x, dfn_x + siz_x - 1]$。

第 $3$ 种情况,则对应的 dfs 序区间为 $[1, dfn_y - 1]\cup [dfn_y + siz_y, n]$。

例题 遥远的国度

洛谷 P3979

给定一棵有 $n$ 个节点的树,初始每个点都有一个点权 $val_i$。

有 $m$ 次操作,每次:

  1. 把根节点更换成 $x$

  2. 将 $x$ 到 $y$ 路径上的所有点的点权都变为某个值

  3. 询问以 $x$ 为根的子树内的最小点权值

$1 \leq n\le 100000,1 \leq m \le 100000,0<val_i<2^{31}$。

参考代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 1e5 + 10, INF = (1 << 30);
int val[MAX_N], rt, n, m;
int fa[MAX_N][25], siz[MAX_N], hson[MAX_N], dep[MAX_N];
int dfn[MAX_N], top[MAX_N], a[MAX_N], dfncnt;
int d[MAX_N << 2], b[MAX_N << 2];
vector<int> G[MAX_N];

inline ll read() {...}
inline void write(ll x) {...}

void dfs1(int u, int pre) {
    siz[u] = 1, hson[u] = -1;
    for(int i = 1; i < 25; i++) {
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
        if(!fa[u][i])   break;
    }
    for(int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        if(v == pre)    continue;
        dep[v] = dep[u] + 1;
        fa[v][0] = u;
        dfs1(v, u);
        siz[u] += siz[v];
        if(hson[u] == -1 || siz[hson[u]] < siz[v])  hson[u] = v;
    }
}
void dfs2(int u, int tp) {
    dfn[u] = ++dfncnt;
    a[dfncnt] = val[u];
    top[u] = tp;
    if(hson[u] == -1)   return;
    dfs2(hson[u], tp);
    for(int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        if(v != fa[u][0] && v != hson[u])   dfs2(v, v);
    }
}
inline int lc(int p)    {return (p << 1);}
inline int rc(int p)    {return (p << 1) | 1;}
inline int mid(int s, int t) {return s + ((t - s) >> 1);}
inline void pu(int p) {d[p] = min(d[lc(p)], d[rc(p)]);}
inline void pd(int p, int s, int t) {
    if(b[p]) {
        d[lc(p)] = d[rc(p)] = b[lc(p)] = b[rc(p)] = b[p];
        b[p] = 0;
    }
}
inline void build_tree(int s, int t, int p) {
    if(s == t) {
        d[p] = a[s];
        return;
    }
    int m = mid(s, t);
    build_tree(s, m, lc(p));
    build_tree(m + 1, t, rc(p));
    pu(p);
}
inline void update(int s, int t, int p, int l, int r, int v) {
    if(l <= s && t <= r) {
        d[p] = b[p] = v;
        return;
    }
    pd(p, s, t);
    int m = mid(s, t);
    if(l <= m)  update(s, m, lc(p), l, r, v);
    if(r > m)   update(m + 1, t, rc(p), l, r, v);
    pu(p);
}
inline int query(int s, int t, int p, int l, int r) {
    if(l <= s && t <= r)    return d[p];
    pd(p, s, t);
    int m = mid(s, t);
    int ret = INF;
    if(l <= m)  ret = min(ret, query(s, m, lc(p), l, r));
    if(r > m)   ret = min(ret, query(m + 1, t, rc(p), l, r));
    return ret;
}
inline void update_path(int x, int y, int v) {
    while(top[x] != top[y]) {
        if(dep[top[x]] < dep[top[y]])   swap(x, y);
        update(1, n, 1, dfn[top[x]], dfn[x], v);
        x = fa[top[x]][0];
    }
    if(dep[x] > dep[y]) swap(x, y);
    update(1, n, 1, dfn[x], dfn[y], v);
}
inline int find(int x, int k) {
    for(int i = 24; i >= 0; i--)
        if(k >= (1 << i))   x = fa[x][i], k -= (1 << i);
    return x; 
}
int main() {
    n = read(), m = read();
    for(int i = 1; i <= n - 1; i++) {
        int u = read(), v = read();
        G[u].push_back(v), G[v].push_back(u);
    }
    for(int i = 1; i <= n; i++)
        val[i] = read();
    dfs1(1, 0), dfs2(1, 1);
    rt = read();
    build_tree(1, n, 1);
    while(m--) {
        int op = read();
        if(op == 1) rt = read();
        else if(op == 2) {
            int x = read(), y = read(), v = read();
            update_path(x, y, v);
        }
        else {
            int x = read(), ans;
            if(x == rt) ans = d[1];
            else if(dep[rt] > dep[x]) {
                int y = find(rt, dep[rt] - dep[x] - 1);
                if(fa[y][0] == x) {
                    ans = query(1, n, 1, 1, dfn[y] - 1);
                    if(dfn[y] + siz[y] <= n)    ans = min(ans, query(1, n, 1, dfn[y] + siz[y], n));
                }
                else    ans = query(1, n, 1, dfn[x], dfn[x] + siz[x] - 1);
            }
            else    ans = query(1, n, 1, dfn[x], dfn[x] + siz[x] - 1);
            write(ans), putchar('\n');
        }
    }
    return 0;
}

Joy © 2023 Powered by Jekyll and Theme by solid

今日诗词API Valine 评论管理 Github