【数据结构】可持久化并查集

事实上是并查集套一个可持久化数组。

int a[100005];

struct SegmentTree {

#define ls lch[o]
#define rs rch[o]

    static const int MAXN = 1e5 + 10;
    static const int MAXM = 2e5 + 10;

    int n;

    int top;
    int lch[MAXN * 2 + MAXM * 18];
    int rch[MAXN * 2 + MAXM * 18];
    int sum[MAXN * 2 + MAXM * 18];

    int ver[MAXM], cur;

    int Clone(int o) {
        ++top;
        sum[top] = sum[o];
        lch[top] = ls;
        rch[top] = rs;
        return top;
    }

    void PushUp(int o) {
        sum[o] = sum[ls] + sum[rs];
    }

    int BuildHelp(int l, int r) {
        int o = Clone(0);
        if(l == r) {
            sum[o] = a[l];
            ls = 0;
            rs = 0;
            return o;
        }
        int m = (l + r) >> 1;
        ls = BuildHelp(l, m);
        rs = BuildHelp(m + 1, r);
        PushUp(o);
        return o;
    }

    int AddHelp(int o, int l, int r, int p, ll v) {
        o = Clone(o);
        if(l == r) {
            sum[o] += v;
            return o;
        }
        int m = (l + r) >> 1;
        if(p <= m)
            ls = AddHelp(ls, l, m, p, v);
        if(p >= m + 1)
            rs = AddHelp(rs, m + 1, r, p, v);
        PushUp(o);
        return o;
    }

    int SetHelp(int o, int l, int r, int p, ll v) {
        o = Clone(o);
        if(l == r) {
            sum[o] = v;
            return o;
        }
        int m = (l + r) >> 1;
        if(p <= m)
            ls = SetHelp(ls, l, m, p, v);
        if(p >= m + 1)
            rs = SetHelp(rs, m + 1, r, p, v);
        PushUp(o);
        return o;
    }

    int SumHelp(int o, int l, int r, int ql, int qr) {
        if(ql <= l && r <= qr)
            return sum[o];
        int m = (l + r) >> 1;
        int res = 0;
        if(ql <= m)
            res = res + SumHelp(ls, l, m, ql, qr);
        if(qr >= m + 1)
            res = res + SumHelp(rs, m + 1, r, ql, qr);
        return res;
    }

    void Build(int _n) {
        n = _n;
        cur = 0;
        top = 0;
        ver[++cur] = BuildHelp(1, n);
    }

    void Add(int id, int pos, int val) {
        ver[++cur] = AddHelp(ver[id], 1, n, pos, val);
    }

    void Set(int id, int pos, int val) {
        ver[++cur] = SetHelp(ver[id], 1, n, pos, val);
    }

    int Sum(int id, int lpos, int rpos) {
        return SumHelp(ver[id], 1, n, lpos, rpos);
    }

};

struct DisjointSet {

    int n;

    SegmentTree find;
    SegmentTree size;

    int ver[200005], cur;

    void Init(int n) {
        cur = 0;
        for(int i = 1; i <= n; i++)
            a[i] = i;
        find.Build(n);
        for(int i = 1; i <= n; i++)
            a[i] = 1;
        size.Build(n);
        ver[++cur] = find.cur;
    }

    int Find(int id, int x) {
        int fx = find.Sum(ver[id], x, x);
        if(fx == x)
            return x;
        return Find(id, fx);
    }

    int Size(int id, int x) {
        x = Find(id, x);
        return size.Sum(id, x, x);
    }

    int Merge(int id, int x, int y) {
        x = Find(id, x);
        y = Find(id, y);
        if(x == y) {
            ver[++cur] = ver[id];
            return 0;
        }
        int sx = size.Sum(ver[id], x, x);
        int sy = size.Sum(ver[id], y, y);
        if(sx < sy) {
            swap(x, y);
            swap(sx, sy);
        }
        find.Set(ver[id], y, x);
        size.Add(ver[id], x, sy);
        ver[++cur] = find.cur;
        return x;
    }

} ds;

这里默认merge操作产生一个新的版本。并查集的ver[u]表示并查集的版本u对应线段树的版本ver[u]

也就是说,并查集的ver[u]对应的线段树根为find.ver[ver[u]],这里find和size的树根始终处于同一版本号,同步变化。
注意分清楚哪些操作产生新的版本。

假如是验证中的例题,跳转操作也产生新的版本,其他操作都在并查集的最后一个版本上修改(和可持久化数组的每个修改都带old版本不同)。

注意并查集的最后一个版本不见得对应线段树的最后一个版本,因为这里为了节省空间使得线段树并不会因为Sum操作产生版本。

posted @ 2021-01-16 10:56  purinliang  阅读(88)  评论(0编辑  收藏  举报