可持久化并查集 / 可持久化 01 Trie 笔记

可持久化并查集

首先,可以把可持久化线段树当可持久化数组用,所以用一个可持久化数组来当父亲数组。

需要一种非均摊的路径压缩方法,考虑按秩合并,其中以树高为秩。

具体地,如果要合并 \(u\)\(v\),让树高较小的一棵往树高较大的一棵合并,再更新树高即可。

如何更新树高?假设 \(u\) 是树高较大的一棵子树,若 \(h_u>h_v\),那么合并后树高不变,所以当且仅当 \(h_u=h_v\) 时,\(v\) 上面接了个 \(u\),所以 \(h_u\gets h_v+1\),即 \(h_u\gets h_u+1\),也可以用一个可持久化数组维护。

可以证明这样合并,可以使得单次查询的复杂度为 \(O(\log n)\)

P3402 可持久化并查集 Code

namespace Segtree {
    int root[N], idx;
    struct TREE {
        int ls, rs, f, dep;
    } t[N];
    void build(int &rt, int l, int r) {
        rt = ++idx;
        if (l == r) {
            t[rt].f = l;
            return;
        }
        int mid = l + r >> 1;
        build(t[rt].ls, l, mid);
        build(t[rt].rs, mid + 1, r);
    }
    void update_f(int &rt, int l, int r, int x, int v) {
        t[++idx] = t[rt];
        rt = idx;
        if (l == r) {
            t[rt].f = v;
            return;
        }
        int mid = l + r >> 1;
        x <= mid ? update_f(t[rt].ls, l, mid, x, v) : update_f(t[rt].rs, mid + 1, r, x, v);
    }
    void update_dep(int &rt, int l, int r, int x) {
        t[++idx] = t[rt];
        rt = idx;
        if (l == r) {
            t[rt].dep++;
            return;
        }
        int mid = l + r >> 1;
        x <= mid ? update_dep(t[rt].ls, l, mid, x) : update_dep(t[rt].rs, mid + 1, r, x);
    }
    TREE query(int rt, int l, int r, int x) {
        if (l == r)
            return t[rt];
        int mid = l + r >> 1;
        return x <= mid ? query(t[rt].ls, l, mid, x) : query(t[rt].rs, mid + 1, r, x);
    }
}
using namespace Segtree;

namespace UDS {
    TREE gf(int x, int ver) {
        TREE f = query(root[ver], 1, n, x);
        return f.f == x ? f : gf(f.f, ver);
    }
    void un(int x, int y, int ver) {
        TREE fx = gf(x, ver), fy = gf(y, ver);
        if (fx.f == fy.f)
            return;
        if (fx.dep < fy.dep)
            swap(fx, fy);
        update_f(root[ver], 1, n, fy.f, fx.f);
        if (fx.dep == fy.dep)
            update_dep(root[ver], 1, n, fx.f);
    }
}
using namespace UDS;

可持久化 01 Trie

仿照可持久化线段树的思路即可,如下面的例题。

P4735 最大异或和

区间求值用前缀和拆分。

\[s_i=\operatorname{xor}_{j=1}^i a_i \]

\[\max_{p\in [l,r]}((\operatorname{xor}_{i=p}^n a_i)\operatorname{xor} x)=\max_{p\in [l,r]}(s_{p-1}\operatorname{xor} (s_n \operatorname{xor} x)) \]

暴力的想法是对于每个 \(p\) 建一棵 01 Trie,但是发现可以用可持久化的思想,“复用”以前的节点。对于区间查询,打一个时间戳,标记某个树上的节点是否在查询的区间内即可。

对于本模板题,异或 \(0\) 是合法的,所以还需要提前插入一个 \(0\)

namespace TRIE {
    int root[N], t[N][2], tim[N], idx;
    void insert(int u, int v, int x, int step) {
        if (step < 0)
            return;
        int c = x >> step & 1;
        t[u][c ^ 1] = t[v][c ^ 1];
        t[u][c] = ++idx;
        tim[t[u][c]] = tim[t[v][c]] + 1;
        insert(t[u][c], t[v][c], x, step - 1);
    }
    int query(int l, int r, int x, int step) {
        if (step < 0)
            return 0;
        int c = x >> step & 1;
        if (tim[t[r][c ^ 1]] > tim[t[l][c ^ 1]])
            return 1 << step | query(t[l][c ^ 1], t[r][c ^ 1], x, step - 1);
        else
            return query(t[l][c], t[r][c], x, step - 1);
    }
}
using namespace TRIE;

signed main() {
    IOS;
    cin >> n >> m;
    root[0] = ++idx;
    insert(root[0], 0, 0, 25);
    for (int i = 1, x; i <= n; i++) {
        cin >> x;
        q[i] = (q[i - 1] ^ x);
        root[i] = ++idx;
        insert(root[i], root[i - 1], q[i], 25);
    }
    for (int i = 1, l, r, x; i <= m; i++) {
        char c;
        cin >> c;
        if (c == 'A') {
            cin >> x;
            n++;
            q[n] = (q[n - 1] ^ x);
            root[n] = ++idx;
            insert(root[n], root[n - 1], q[n], 25);
        } else {
            cin >> l >> r >> x;
            l--, r--;
            if (!l)
                cout << query(0, root[r], q[n] ^ x, 25) << "\n";
            else
                cout << query(root[l - 1], root[r], q[n] ^ x, 25) << "\n";
        }
    }
    return 0;
}
posted @ 2025-02-21 21:50  Garbage_fish  阅读(85)  评论(2)    收藏  举报