zoukankan      html  css  js  c++  java
  • 带撤销并查集 & 可持久化并查集

    带撤销并查集支持从某个元素从原来的集合中撤出来,然后加入到一个另外一个集合中,或者删除该元素

    用一个映射来表示元素和并查集中序号的关系,代码中用(to[x]) 表示x号元素在并查集中的 id

    删除 x 号元素时,需要将 (to[x]) 的集合大小减去1,然后令 (to[x]=-1) 标记 x 删除即可

    如果要重新加入一个元素,那么给x分配一个新的 id,(to[x] = newid)

    例题1:https://www.cometoj.com/contest/33/problem/E?problem_id=1459

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 4000010;
    int fa[N],to[N],sz[N],cnt,a[N],b[N],tot;
    int n,m,k,x,y;
    int ok[N];
    int find(int x){
        return x == fa[x]?x:fa[x] = find(fa[x]);
    }
    void merge(int x,int y){
        int a = find(to[x]);
        int b = find(to[y]);
        if(a==b) return;
        fa[b] = a;
        sz[a] += sz[b]; sz[b] = 0;
    }
    // 将x从原集合删除,加入到 y 所属的集合
    void update(int x,int y){
        sz[find(to[x])]--;
        to[x] = ++cnt;//给x分配新的 id
        sz[cnt] = 1;
        fa[cnt] = cnt;
        merge(x,y);
    }
    int main(){
        scanf("%d%d",&n,&m);cnt = n;
        for(int i=1;i<=n;i++)fa[i] = i,to[i] = i,sz[i] = 1;
        for(int i=1;i<=m;i++){
            scanf("%d%d",&k,&x);if(k!=3)scanf("%d",&y);
            if(k == 5){
                a[++tot] = x;
                b[tot] = y;continue;
            }
            if(k == 1)merge(x,y);
            if(k == 2)update(x,y);
            if(k == 4){
                if(find(to[x]) == find(to[y]))printf("Yes
    ");
                else printf("No
    ");
            }
    
            if(k == 3)printf("%d
    ",sz[find(to[x])]-1);
        }
        
        for(int i=1;i<=tot;i++){
            if(find(to[a[i]]) == find(to[b[i]])) ok[find(to[a[i]])] = 1;
        }
        int res = -1;
        for(int i=1;i<=n;i++)
            if(!ok[find(to[i])])res = max(res,sz[find(to[i])]);
        cout<<res<<endl;
        return 0;
    }
    

    例题2:https://nanti.jisuanke.com/t/42576
    [2019南昌区域赛A]
    不要求在线的可持久化操作,可以离线处理询问,按照正常的时间顺序维护并查集,由于需要回溯,所以不能使用路径压缩
    复杂度 (O(mlog n))

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int inf = 0x3f3f3f3f;
    #define dbg(x...) do { cout << "33[32;1m" << #x <<" -> "; err(x); } while (0)
    void err() { cout << "33[39;0m" << endl; }
    template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
    const int N = 1000000 + 50;
    struct Node{
        int op, x, y;
    }o[N];
    vector<int> g[N];
    int n, m;
    int fa[N*2], dep[N*2], sz[N*2], to[N], tot;
    int res[N];
    int find(int x){
        if(x == fa[x]) return x;
        return find(fa[x]);
    }
    /*
    1 k a b merge(a, b)
    2 k a  del(a)
    3 k a b merge(a,b)
    4 k a b find(a) == find(b)
    5 k a  sz[a]
    */
    void dfs(int u){
        for(auto id : g[u]){
            int op = o[id].op, x = o[id].x, y = o[id].y;
            if(op == 1){
                if(to[x] == -1 || to[y] == -1){
                    dfs(id);
                    continue;
                }
                int rx = find(to[x]), ry = find(to[y]);
                if(rx == ry){ dfs(id); continue;}
                else if(dep[rx] == dep[ry]){
                    fa[ry] = rx; sz[rx] += sz[ry]; dep[rx] ++;
                    dfs(id);
                    fa[ry] = ry; sz[rx] -= sz[ry]; dep[rx] --;
                } else {
                    if(dep[rx] < dep[ry]) swap(rx, ry);
                    fa[ry] = rx; sz[rx] += sz[ry];
                    dfs(id);
                    fa[ry] = ry; sz[rx] -= sz[ry];
                }
            } else if(op == 2){
                if(to[x] == -1) { dfs(id); continue; }
                int rx = find(to[x]);
                int t = to[x];
                to[x] = -1;
                sz[rx] --;
                dfs(id);
                to[x] = t;
                sz[rx] ++;
            } else if(op == 3){
                if(to[x] == - 1 || to[y] == -1) {dfs(id); continue; }
                int t = to[x];
                int rx = find(to[x]), ry = find(to[y]);
                sz[rx]--;
                to[x] = ++tot;
                sz[to[x]] = 1;
                fa[to[x]] = ry;
                sz[ry] ++;
                dfs(id);
                sz[ry] --;
                sz[rx] ++;
                to[x] = t;
            } else {
                if(op == 4){
                    if(to[x] == -1 || to[y] == -1){
                        res[id] = 0;
                    } else {
                        res[id] = find(to[x]) == find(to[y]);
                    }
                } else if(op == 5){
                    if(to[x] == -1) res[id] = 0;
                    else {
                        res[id] = sz[find(to[x])];
                    }
                }
                dfs(id);
            }
        }
    }
    int main(){
        scanf("%d%d", &n, &m);
        for(int i=1;i<=m;i++){
            int op, k, x, y = 0;scanf("%d%d%d", &op, &k, &x);
            g[k].push_back(i);
            if(op != 2 && op != 5) scanf("%d", &y);
            o[i] = {op, x, y};
        }
        for(int i=1;i<=n;i++) to[i] = i, fa[i] = i, sz[i] = 1;
        tot = n;
        dfs(0);
        for(int i=1;i<=m;i++){
            if(o[i].op == 4) puts(res[i] ? "Yes" : "No");
            else if(o[i].op == 5){
                printf("%d
    ", res[i]);
            }
        }
        return 0;
    }
    

    可持久化并查集支持查询任一历史版本的信息。并查集信息用数组 (fa) 表示,合并集合时,基本操作有两种,一个是路径压缩(可能会修改很多个fa),一个是按秩合并(启发式合并思路,小的集合向大的合并,只会修改一次fa),由于要保存历史信息,按照可持久化的一贯思路,每一次操作都会新开 logn 的空间,所以这里要用按秩合并。

    注意按秩合并的find函数写法也有所不同,不能修改fa

    此时问题就是维护每个版本的fa数组

    模版题:https://www.luogu.com.cn/problem/P3402

    const int N = 200000 + 5;
    int n, m;
    struct TreeNode{
        int l, r;
    }t[N*30];
    int root[N], tot;
    int fa[N*30], dep[N*30];
    void build(int &p, int l, int r){
        p = ++tot;
        if(l == r){fa[p] = l;return;}
        int mid = l + r >> 1;
        build(t[p].l, l, mid);
        build(t[p].r, mid+1, r);
    }
    // 修改p版本的pos位置的fa值
    void change(int last, int &p, int l, int r, int pos, int val){
        p = ++tot;
        t[p] = t[last];
        if(l == r){
            fa[p] = val;
            dep[p] = dep[last];
            return;
        }
        int mid = l + r >> 1;
        if(pos <= mid)
            change(t[last].l, t[p].l, l, mid, pos, val);
        else 
            change(t[last].r, t[p].r, mid+1, r, pos, val);
    } 
    int getIndex(int p, int l, int r,int pos){
        if(l == r) return p;
        int mid = l + r >> 1;
        if(pos <= mid) return getIndex(t[p].l, l, mid, pos);
        else return getIndex(t[p].r, mid+1, r, pos);
    }
    int find(int p, int x){
        int index = getIndex(p, 1, n, x);
        if(fa[index] == x) return index; // 返回祖先节点下标
        return find(p, fa[index]);
    }
    void merge(int last, int &p, int x, int y){
        int fx = find(p, x), fy = find(p, y);
        if(fa[fx] == fa[fy]) return;
        // x-> y合并,需要满足 dep[x]<dep[y]
        if(dep[fx] > dep[fy]) swap(fx, fy);
        change(last, p, 1, n, fa[fx], fa[fy]);
        if(dep[fx] == dep[fy]){
            dep[getIndex(p, 1, n, fa[fy])]++;
        }
    }
    int main(){
        scanf("%d%d", &n, &m);
        build(root[0], 1, n);
        for(int i=1;i<=m;i++){
            root[i] = root[i-1];
            int op, x, y;scanf("%d%d", &op, &x);
            if(op == 1){
                scanf("%d", &y);
                merge(root[i], root[i], x, y);
            } 
            else if(op == 2) root[i] = root[x];
            else {
                scanf("%d", &y);
                int fx = find(root[i], x), fy = find(root[i], y);
                if(fa[fx] == fa[fy]) puts("1");
                else puts("0");
            }
        }
        return 0;
    }
    

    例题:https://www.luogu.com.cn/problem/P4768

    先跑一次最短路,然后按照边权从大到小用带权并查集维护集合到源点的最短距离。保留所有版本的信息,然后对于每次查询找到对应版本号回答问题即可

    复杂度:(O(nlog m + (m+q) log^2n))

  • 相关阅读:
    winform中的 listview的使用的讲解。
    快乐的一天从AC开始 | 20210716 | P1345
    快乐的一天从AC开始 | 20210715 | P4643
    快乐的一天从AC开始 | 20210714 | P3594
    快乐的一天从AC开始 | 20210713 | P3557
    快乐的一天从AC开始 | 20210712 | P2251
    P7294-[USACO21JAN]Minimum Cost Paths P【单调栈】
    AT4353-[ARC101D]Robots and Exits【LIS】
    2021牛客暑期多校训练营9C-Cells【LGV引理,范德蒙德行列式】
    Loj#2880-「JOISC 2014 Day3」稻草人【CDQ分治,单调栈,二分】
  • 原文地址:https://www.cnblogs.com/1625--H/p/12795578.html
Copyright © 2011-2022 走看看