可撤销并查集

用途

  • 顾名思义, 可以将以前的 Merge 操作撤销
  • 但是只能从后往前一步一步撤销, 做不到像主席树一样直接访问历史版本
  • 多用于树上问题

原理

  • 按秩合并
    • 将集合 x 和 y 合并时, 如果 siz[x] > siz[y], 那么就把 y 的父亲设为 x, 反之亦然
    • (和启发式合并有点像)时间复杂度 \(O(log n)\)
  • 用栈维护操作序列

过程

  • 数组
    int s[N], si[N], cnt;	// 记录 父亲, 集合大小, 用于初始化
    int st[N], tp;	// 栈, 栈顶下标
  • Find
    int Find(int x){
        if(x != s[x]){
            return Find(s[x]);	// 不能路径压缩, 否则就没法撤销了
        }
        return s[x];
    }
  • Merge
    void Merge(int x, int y){
        int fa = Find(x);
        int fb = Find(y);
        if(fa == fb){
            return;
        }
        if(si[fa] < si[fb]){	// 小的接在大的下面
            swap(fa, fb);
        }
        s[fb] = fa;
        si[fa] += si[fb];	// 更新较大的集合的 size
        st[++tp] = fb;	// 记录该步操作的较小集合, 用于撤销
    }
  • Delete
    void Delete(int x){
        while(tp > x){	// 目标是撤销到第 x 步操作
            int k = st[tp];
            si[s[k]] -= si[k];	// 更新较大集合的 size
            s[k] = k;	// 将较小集合分离出来
            tp--;
        }
    }

完整代码

// Union ans Find Set
struct Ufs{
    int s[N], si[N], cnt;
    int st[N], tp;

    inline void Insert(){
        ++cnt;
        s[cnt] = cnt;
        si[cnt] = 1;
    }

    int Find(int x){
        if(x != s[x]){
            return Find(s[x]);
        }
        return s[x];
    }

    void Merge(int x, int y){
        int fa = Find(x);
        int fb = Find(y);
        if(fa == fb){
            return;
        }
        if(si[fa] < si[fb]){
            swap(fa, fb);
        }
        s[fb] = fa;
        si[fa] += si[fb];
        st[++tp] = fb;
    }

    void Delete(int x){
        while(tp > x){
            int k = st[tp];
            si[s[k]] -= si[k];
            s[k] = k;
            tp--;
        }
    }
}s;
posted on 2024-03-27 20:08  Bubble_e  阅读(809)  评论(0)    收藏  举报