关于毛毛虫剖分

毛毛虫剖分

一种由重链剖分推广而成的树上结点重标号方法,支持 修改/查询 一只毛毛虫的信息,并且可以对毛毛虫的身体和足分别 修改/查询 不同信息 。

可以用来解决一些大力树剖也可以解决的问题。

一些定义:

  • 毛毛虫:一条树上的链和与这条链邻接的所有结点构成的集合;
  • 虫身:毛毛虫的链部分;
  • 虫足:毛毛虫除虫身的部分。

重标号方法:

  • 首先重剖求出重链。若现在递归处理到结点 u:

  • 若 u 还未被标号,则为其标号;

  • 若 u 是链头,遍历这条重链,将邻接这条链的结点依次标号;

  • 先递归重儿子,再递归轻儿子。

重标号性质:

  • 对于重链,除链头外的结点标号连续;

  • 对于任意结点,其轻儿子标号连续;

  • 对于以重链头为根的子树,与这条重链邻接的所有结点标号连续;

借此我们可以很容易地求维护每条毛毛虫的信息。

同时也能顺便维护重链链分的所有信息以及子树的所有信息(一棵子树至多剖分为三个不交区间:重链区间、邻接轻点区间、邻接轻子树区间),复杂度与重链剖分完全一样。

例题:

[NOI2021] 轻重边

对原树进行毛毛虫剖分后,要支持对一条毛毛虫身体染黑色,足部染白色,查询一条链上的黑点数量。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
int T;
int n, m;
int ver[200005], ne[200005], head[100005], tot;
inline void link(int x, int y) {
    ver[++tot] = y;
    ne[tot] = head[x];
    head[x] = tot;
}
int siz[100005], son[100005], fa[100005], dep[100005];
void dfs1(int x, int fi) {
    siz[x] = 1; fa[x] = fi; dep[x] = dep[fi] + 1;
    for (int i = head[x]; i; i = ne[i]) {
        int u = ver[i];
        if (u == fi) continue;
        dfs1(u, x); siz[x] += siz[u];
        if (siz[u] > siz[son[x]]) son[x] = u;
    }
}
int top[100005];
int dfn[100005], cnt, lein[100005], leout[100005], alout[100005], subin[100005], subout[100005];
void cover(int x) {
    lein[x] = cnt + 1;
    for (int i = head[x]; i; i = ne[i]) {
        int u = ver[i];
        if (u == fa[x] || u == son[x]) continue;
        dfn[u] = ++cnt;
    }
    leout[x] = cnt;
    if (son[x]) cover(son[x]);
    alout[x] = cnt;
}
void dfs2(int x, int fi) {
    top[x] = fi;
    if (!dfn[x]) dfn[x] = ++cnt;
    if (x == top[x]) cover(x);
    subin[x] = cnt + 1;
    if (son[x]) dfs2(son[x], fi);
    for (int i = head[x]; i; i = ne[i]) {
        int u = ver[i];
        if (u == fa[x] || u == son[x]) continue;
        dfs2(u, u);
    }
    subout[x] = cnt;
}
int tree[400005], lazy[400005];
void build(int l = 1, int r = n, int i = 1) {
    tree[i] = 0; lazy[i] = -1;
    if (l == r) return ;
    int mid = (l + r) >> 1;
    build(l, mid, i << 1);
    build(mid + 1, r, i << 1 | 1);
}
inline void push(int i, int l, int r) {
    int mid = (l + r) >> 1;
    tree[i << 1] = lazy[i] * (mid - l + 1); lazy[i << 1] = lazy[i];
    tree[i << 1 | 1] = lazy[i] * (r - mid); lazy[i << 1 | 1] = lazy[i];
    lazy[i] = -1;
}
void update(int fr, int to, int v, int l = 1, int r = n, int i = 1) {
    if (fr > r || to < l) return ;
    if (fr <= l && to >= r) {
        tree[i] = (r - l + 1) * v; lazy[i] = v;
        return ;
    }
    if (~lazy[i]) push(i, l, r);
    int mid = (l + r) >> 1;
    update(fr, to, v, l, mid, i << 1); update(fr, to, v, mid + 1, r, i << 1 | 1);
    tree[i] = tree[i << 1] + tree[i << 1 | 1];
}
inline void lca(int x, int y) {
    vector<int> vec;
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) swap(x, y);
        update(lein[top[x]], leout[x], 0);
        update(dfn[son[x]], dfn[son[x]], 0);
        if (x != top[x]) update(dfn[son[top[x]]], dfn[x], 1);
        vec.push_back(top[x]);
        x = fa[top[x]];
    }
    if (dep[x] < dep[y]) swap(x, y);
    update(lein[y], leout[x], 0);
    update(dfn[y], dfn[y], 0);
    update(dfn[son[x]], dfn[son[x]], 0);
    if (x != y) update(dfn[son[y]], dfn[x], 1);
    for (auto it : vec) update(dfn[it], dfn[it], 1);
}
int query(int fr, int to, int l = 1, int r = n, int i = 1) {
    if (fr > r || to < l) return 0;
    if (fr <= l && to >= r) return tree[i];
    if (~lazy[i]) push(i, l, r);
    int mid = (l + r) >> 1;
    return query(fr, to, l, mid, i << 1) + query(fr, to, mid + 1, r, i << 1 | 1);
}
inline int Query(int x, int y) {
    int res = 0;
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) swap(x, y);
        if (top[x] != x) res += query(dfn[son[top[x]]], dfn[x]);
        res += query(dfn[top[x]], dfn[top[x]]);
        x = fa[top[x]];
    }
    if (dep[x] < dep[y]) swap(x, y);
    if (x != y) res += query(dfn[son[y]], dfn[x]);
    return res;
}
inline void clear() {
    for (int i = 1; i <= n; i++) head[i] = 0;
    tot = 0;
    for (int i = 1; i <= n; i++) siz[i] = 0;
    for (int i = 1; i <= n; i++) son[i] = 0;
    for (int i = 1; i <= n; i++) dfn[i] = 0;
    cnt = 0;
    build();
}
inline void solve() {
    scanf("%d%d", &n, &m);
    clear();
    for (int i = 1; i < n; i++) {
        int x, y; scanf("%d%d", &x, &y);
        link(x, y); link(y, x);
    }
    dfs1(1, 1);
    dfs2(1, 1);
    while (m--) {
        int op, x, y; scanf("%d%d%d", &op, &x, &y);
        if (op == 1) lca(x, y);
        else printf("%d\n", Query(x, y));
    }

}
int main() {
    freopen("edge.in", "r", stdin);
    freopen("edge.out", "w", stdout);
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}


基础树剖练习题

给定一棵 \(n\) 个点以 \(1\) 为根的有根树,边有颜色,初始时全为白色。

接下来有三种操作:

  • \(1\ u\) 表示修改 \(u\) 到根路径上的边的颜色,具体修改方式在下文说明。
  • \(2\ u\) 表示查询 \(u\) 到根路径上的黑边的个数。
  • \(3\ u\) 表示查询 \(u\) 子树内黑边的个数。

修改方式:首先,将从 \(u\) 到根的链拉出来,即为 \(lis_1,lis_2,...,lis_m\),其中 \(lis_i=u,lis_m=1\)\(\forall 1≤i<m\) \(lis_{i+1}=fa_{lst_i}\)

然后,除去 \(u\) 到根路径上的边,将其余与 \(lis_1,lis_3,lis 5,...\) 相连的边染成黑色,将与 \(lis_2,lis_4,lis_6,...\) 相连的边染成白色。

最后,对于在 \(u\) 到根路径上的边,将 \((lis_1,lis_2)\)\((lis_3,lis_4)\)\((lis_5,lis_6)\)\(...\) 染成黑色, 将 \((lis_2,lis_3)\)\((lis_4,lis_5)\)\((lis_6,lis_7)\)\(...\) 染成白色。

对原树进行剖分后,对于 \(1\) 操作,我们要对毛毛虫身体上与 \(x\) 深度奇偶性相同的点染黑,不同的染白,足部奇偶性不同的点染黑,相同的染白,\(2,3\) 操作查询在链上和子树内的黑点个数即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n, q;
int ver[200005], ne[200005], head[100005], tot;
inline void link(int x, int y) {
    ver[++tot] = y;
    ne[tot] = head[x];
    head[x] = tot;
}
int siz[100005], son[100005], fa[100005], dep[100005];
void dfs1(int x, int fi) {
    siz[x] = 1;
    dep[x] = dep[fi] + 1;
    fa[x] = fi;
    for (int i = head[x]; i; i = ne[i]) {
        int u = ver[i];
        if (u == fi) continue;
        dfs1(u, x); siz[x] += siz[u];
        if (siz[u] > siz[son[x]]) son[x] = u;
    }
}
int dfn[100005], cnt, top[100005];
int lein[100005], leout[100005], subin[100005], subout[100005], alout[100005];
void cover(int x) {
    lein[x] = cnt + 1;
    for (int i = head[x]; i; i = ne[i]) {
        int u = ver[i];
        if (u == fa[x] || u == son[x]) continue;
        dfn[u] = ++cnt;
    }
    leout[x] = cnt;
    if (son[x]) cover(son[x]);
    alout[x] = cnt;
}
void dfs2(int x, int fi) {
    top[x] = fi;
    if (!dfn[x]) dfn[x] = ++cnt;
    if (x == top[x]) cover(x);
    subin[x] = cnt + 1;
    if (son[x]) dfs2(son[x], fi);
    for (int i = head[x]; i; i = ne[i]) {
        int u = ver[i];
        if (u == fa[x] || u == son[x]) continue;
        dfs2(u, u);
    }
    subout[x] = cnt;
}
int tree[3][400005], lazy[400005];
void insert(int loc, int v, int l = 1, int r = n, int i = 1) {
    if (loc < l || loc > r) return;
    lazy[i] = -1;
    if (l == r) {
        tree[v][i] = 1;
        return;
    }
    int mid = (l + r) >> 1;
    insert(loc, v, l, mid, i << 1);
    insert(loc, v, mid + 1, r, i << 1 | 1);
    tree[0][i] = tree[0][i << 1] + tree[0][i << 1 | 1];
    tree[1][i] = tree[1][i << 1] + tree[1][i << 1 | 1];
}
inline void push(int i) {
    tree[2][i << 1] = tree[lazy[i]][i << 1];
    lazy[i << 1] = lazy[i];
    tree[2][i << 1 | 1] = tree[lazy[i]][i << 1 | 1];
    lazy[i << 1 | 1] = lazy[i];
    lazy[i] = -1;
}
void update(int fr, int to, int v, int l = 1, int r = n, int i = 1) {
    if (fr > r || to < l) return;
    if (fr <= l && to >= r) {
        tree[2][i] = tree[v][i]; lazy[i] = v;
        return;
    }
    if (~lazy[i]) push(i);
    int mid = (l + r) >> 1;
    update(fr, to, v, l, mid, i << 1);
    update(fr, to, v, mid + 1, r, i << 1 | 1);
    tree[2][i] = tree[2][i << 1] + tree[2][i << 1 | 1];
}
int query(int fr, int to, int l = 1, int r = n, int i = 1) {
    if (fr > r || to < l) return 0;
    if (fr <= l && to >= r) return tree[2][i];
    if (~lazy[i]) push(i);
    int mid = (l + r) >> 1;
    return query(fr, to, l, mid, i << 1) + query(fr, to, mid + 1, r, i << 1 | 1);
}
inline void lca(int x, int y, int v) {
    vector<int> vec;
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) swap(x, y);
        update(lein[top[x]], leout[x], v ^ 1);
        update(dfn[son[x]], dfn[son[x]], v ^ 1);
        if (x != top[x]) update(dfn[son[top[x]]], dfn[x], v);
        vec.push_back(top[x]); x = fa[top[x]];
    }
    if (dep[y] > dep[x]) swap(x, y);
    update(lein[y], leout[x], v ^ 1);
    update(dfn[son[x]], dfn[son[x]], v ^ 1);
    if (x != y) update(dfn[son[y]], dfn[x], v);
    for (auto it : vec) update(dfn[it], dfn[it], v);
}
inline int Query(int x, int y) {
    int res = 0;
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) swap(x, y);
        if (x != top[x]) res += query(dfn[son[top[x]]], dfn[x]);
        res += query(dfn[top[x]], dfn[top[x]]); x = fa[top[x]];
    }
    if (dep[y] > dep[x]) swap(x, y);
    if (x != y) res += query(dfn[son[y]], dfn[x]);
    return res;
}
int main() {
    freopen("chain.in", "r", stdin);
    freopen("chain.out", "w", stdout);
    scanf("%d", &n);
    for (int i = 2; i <= n; i++) {
        int x; scanf("%d", &x);
        link(i, x); link(x, i);
    }
    dfs1(1, 1);
    dfs2(1, 1);
    for (int i = 2; i <= n; i++) insert(dfn[i], dep[i] & 1);
    scanf("%d", &q);
    while (q--) {
        int op, x;
        scanf("%d%d", &op, &x);
        if (op == 1)
            lca(1, x, dep[x] & 1);
        if (op == 2)
            printf("%d\n", Query(1, x));
        if (op == 3)
            printf("%d\n", query(lein[x], alout[x]) + query(subin[x], subout[x]));
    }

    return 0;
}


posted @ 2022-07-01 16:52  一粒夸克  阅读(767)  评论(2编辑  收藏  举报