毛毛虫剖分

疯狂写博客是真的爽。

学这个东西的原因是昨天早上的省选模拟赛T1好像用到了这玩意,应该是轻重边的改版?但是我学完了还是不会做。

正好之前有学长在校 OJ 上发过,也就浅浅地学了一下。能在考场上推出来这个东西真的很厉害!

不得不说学长写的文章真的很精炼,写完之后发现自己写的还是啰嗦了。


例题

毛毛虫剖分是基于树链剖分(重链剖分)的,所以会重链剖分也是必要的。

不考虑给结点染色这种巧妙的方法,先把对「边」的操作转化到「点」上,令一个点代表它与它父亲相连的边,根节点没有意义。

那么我们把修改涉及到的点提取出来,我们会发现这个子图像一只毛毛虫,这也是这种剖分方式的命名由来。

搬一下一些定义:

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

对于例题,我们要做的就是虫足赋值为 $0$,虫身赋值为 $1$,然后再做链上求和。

虫身赋 $1$ 很好做,但是虫足怎么办?

会发现虫足不好处理的原因在于其标号不是连续的,无法用以区间修改的方式维护它。

重链剖分做到了在一条重链上结点的编号连续,并且一条路径经过的重链数量为 $\log n$ 级别,这两点是重链剖分的时间复杂度保证。

对于第二点,我们可以继续沿用,所以只需要考虑对结点重新编号。这一次,我们优先考虑让虫足的编号连续。

假设我们已经重链剖分好了,考虑对这些结点再次 dfs 编号。

  1. 如果当前结点没编号则给它编号。
  2. 如果当前结点是一条重链的链顶,那么遍历这条重链,不对重链上的其它结点编号,依次给与这条链上相邻的结点编号(如果有编号了就不编)。
  3. 递归遍历重儿子,再递归遍历轻儿子。

这种标号方法做到了:

  • 重链上除了链顶的标号,其余结点标号连续。
  • 与重链相邻的结点标号连续(链顶的父亲除外)。
  • 每个结点轻儿子标号连续

手画两下,总结得到:这个东西做到了在重链剖分原有的性质不变的情况下保证了虫足的标号连续

如此美妙的性质!于是对于虫足我们也可以使用区间修改的方式进行维护了!

(真的很佩服学长能在考场上想到这种东西。)

不过学长没给代码,好像题解区里也没这个人实现这个东西,所以我只好自己对着文章 yy 了半天才实现出来。

毛毛虫剖分的优点在于:它能在时间复杂度不变的情况下方便地维护虫足的信息。但是相应地,缺点也非常明显:常数比普通树剖大了不止一倍,在对虫身虫足进行操作时的 corner case 也非常多,考场上要谨慎打这个东西,平时打着玩玩就行。

不过我觉得我挺喜欢这种简单暴力无脑的方法

放一份代码,因为卡过常可能或很丑,但是如果你会重链剖分的话只看 dfs3,change(不是线段树的)和 ask(不是线段树的)就好,相信你能看懂的:)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
char ibuf[10000000], *p1 = ibuf;
void read(int& x) {x = 0; char ch = *p1++; while(ch < '0' || ch > '9') ch = *p1++; while(ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = *p1++;}
int t, n, m, op, u, v, cnt;
int fa[100005], dep[100005], siz[100005], son[100005];
int dfn[100005], top[100005];
int id[100005], st[100005], ed[100005];
vector<int> g[100005];
struct Segment_Tree {
    int L, R, val, l[400005], r[400005], sum[400005], tag[400005];
    #define lc (k << 1)
    #define rc (lc | 1)
    #define mid ((l[k] + r[k]) >> 1)
    void build(int k) {
        sum[k] = 0, tag[k] = -1;
        if(l[k] == r[k]) return;
        l[lc] = l[k], r[lc] = mid, l[rc] = mid + 1, r[rc] = r[k];
        build(lc), build(rc);
    }
    void push_down(int k) {
        if(~tag[k]) {
            sum[lc] = tag[k] ? (r[lc] - l[lc] + 1) : 0, sum[rc] = tag[k] ? (r[rc] - l[rc] + 1) : 0;
            tag[lc] = tag[rc] = tag[k];
            tag[k] = -1;
        }
    }
    void change(int k) {
        if(L > R) return;
        if(L <= l[k] && r[k] <= R) {
            sum[k] = val ? (r[k] - l[k] + 1) : 0;
            tag[k] = val;
            return;
        }
        push_down(k);
        if(L <= mid) change(lc);
        if(R > mid) change(rc);
        sum[k] = sum[lc] + sum[rc];
    }
    int ask(int k) {
        if(L <= l[k] && r[k] <= R) return sum[k];
        push_down(k);
        int ret = 0;
        if(L <= mid) ret += ask(lc);
        if(R > mid) ret += ask(rc);
        sum[k] = sum[lc] + sum[rc];
        return ret;
    }
    void change(const int& lt, const int& rt, const int& v) {
        L = lt, R = rt, val = v;
        return change(1);
    }
    int ask(const int& lt, const int& rt) {
        L = lt, R = rt;
        return ask(1);
    }
} tree;
void dfs1(int now) {
    dep[now] = dep[fa[now]] + 1, siz[now] = 1, son[now] = 0;
    for(const auto& i : g[now]) {
        if(i != fa[now]) {
            fa[i] = now;
            dfs1(i);
            siz[now] += siz[i];
            if(siz[i] > siz[son[now]]) son[now] = i;
        }
    }
}
void dfs2(int now) {
    if(son[fa[now]] == now) top[now] = top[fa[now]];
    else top[now] = now;
    if(son[now]) dfs2(son[now]);
    for(const auto& i : g[now]) {
        if(i != fa[now] && i != son[now]) {
            dfs2(i);
        }
    }
}
void dfs3(int now) {
    if(!id[now]) id[now] = ++cnt;
    if(top[now] == now) {
        int tmp = now;
        while(true) {
            st[tmp] = cnt + 1;
            for(const auto& i : g[tmp]) {
                if(i != fa[tmp] && i != son[tmp]) {
                    id[i] = ++cnt;
                }
            }
            ed[tmp] = cnt;
            if(!son[tmp]) break;
            tmp = son[tmp];
        }
    }
    if(son[now]) dfs3(son[now]);
    for(const auto& i : g[now]) {
        if(i != fa[now] && i != son[now]) {
            dfs3(i);
        }
    }
}
void change(int x, int y) {
    const int _x = x, _y = y;
    while(top[x] != top[y]) {
        if(dep[top[x]] < dep[top[y]]) swap(x, y);
        if(son[x]) tree.change(id[son[x]], id[son[x]], 0);
        tree.change(st[top[x]], ed[x], 0);
        x = fa[top[x]];
    }
    if(dep[x] > dep[y]) swap(x, y);
    tree.change(id[x], id[x], 0);
    if(son[x]) tree.change(id[son[x]], id[son[x]], 0);
    if(son[y]) tree.change(id[son[y]], id[son[y]], 0);
    tree.change(st[x], ed[y], 0);

    x = _x, y = _y;
    while(top[x] != top[y]) {
        if(dep[top[x]] < dep[top[y]]) swap(x, y);
        if(x != top[x]) {
            tree.change(id[son[top[x]]], id[x], 1);
            x = top[x];
        }
        tree.change(id[x], id[x], 1);
        x = fa[x];
    }
    if(dep[x] > dep[y]) swap(x, y);
    if(x != y) tree.change(id[son[x]], id[y], 1);
}
int ask(int x, int y) {
    int ret = 0;
    while(top[x] != top[y]) {
        if(dep[top[x]] < dep[top[y]]) swap(x, y);
        if(x != top[x]) {
            ret += tree.ask(id[son[top[x]]], id[x]);
            x = top[x];
        }
        ret += tree.ask(id[x], id[x]);
        x = fa[x];
    }
    if(dep[x] > dep[y]) swap(x, y);
    if(x != y) ret += tree.ask(id[son[x]], id[y]);
    return ret;
}
int main() {
    fread(ibuf, 1, 10000000, stdin);
    ios::sync_with_stdio(0);
    cout.tie(0);
    read(t);
    while(t--) {
        read(n), read(m);
        for(int i = 1; i <= n; ++i) {
            g[i].clear();
            id[i] = 0;
        }
        for(int i = 2; i <= n; ++i) {
            read(u), read(v);
            g[u].push_back(v);
            g[v].push_back(u);
        }
        dfs1(1);
        dfs2(1);
        cnt = 0;
        dfs3(1);
        tree.l[1] = 1, tree.r[1] = n;
        tree.build(1);
        while(m--) {
            read(op), read(u), read(v);
            if(op & 1) change(u, v);
            else cout << ask(u, v) << '\n';
        }
    }
    return 0;
}
posted @ 2023-12-22 22:31  A_box_of_yogurt  阅读(23)  评论(0编辑  收藏  举报  来源
Document