可持久化并查集

可持久化并查集

说实在话,可持久化并查集在理解上没有太多困难,就是细节比较多,简单来说,所有在并查集上的对数组的操作全部变成在可持久化数组上的操作就可以了,下面我们一个一个函数的分析。

1 代码实现

首先给出例题链接:链接

1.1 定义

int n,m;
struct node{
    int l,r;
};
int root[N],fa[N*40],deep[N*40],tail;
node p[N*40];

这是可持久化并查集所有变量,其中 \(n,m\) 如题目所示,\(node\) 中存的是左儿子和右儿子,\(root\) 是根,\(fa\) ,\(deep\) 是并查集上的节点深度和集合代表。

注意:\(fa,deep\) 全部是给叶子节点准备的,这两个数组的下标是叶子节点在树上的编号,但存的是数组上的下标。

下面我们用动态开点来完成所有操作。

1.2 动态开点:

    inline int new_node(){
        size++;p[size].l=p[size].r=0;return size;
    }

其中 \(size\) 是节点总数。

1.3 初始化并查集

    inline void build(int &k,int l,int r){
        k=new_node();
        if(l==r){fa[k]=l;return;}
        int mid=(l+r)>>1;build(p[k].l,l,mid);build(p[k].r,mid+1,r);
    }

非常简单的初始化。时刻不要忘记 \(fa\) 数组是对应的叶子节点在树上的编号。

1.4 获得位置

    inline int getposi(int k,int l,int r,int x){
        if(l==r) return k;
        int mid=(l+r)>>1;
        if(x<=mid) return getposi(p[k].l,l,mid,x);
        else return getposi(p[k].r,mid+1,r,x);
    }

这个函数的目的是返回在第 \(k\) 个版本的并查集中,返回数组下标 \(x\) 对应的树上编号是多少。

1.5 找到集合代表

在可持久化并查集中,不能够压缩路径,这样会炸空间时间,只能按秩合并。

    inline int find(int k,int x){
        int now=getposi(k,1,n,x);
        if(fa[now]==x) return now;
        else return find(k,fa[now]);
    }

注意,这里 \(x\) 是数组下标,但是这个函数返回的是集合代表对应在树上的编号。仍然是在版本 \(k\) 中。

1.6 单点修改

可持久化数组经典操作,单点修改。

    inline void change(int &k,int last,int l,int r,int x,int val){
        k=new_node();
        p[k]=p[last];
        if(l==r){deep[k]=deep[last];fa[k]=val;return;}
        int mid=(l+r)>>1;
        if(x<=mid) change(p[k].l,p[last].l,l,mid,x,val);
        else change(p[k].r,p[last].r,mid+1,r,x,val);
    }

不做过多讲解。这里主要是改变 \(fa\) 。这里 \(x,val\) 都是数组下标。

1.7 更新深度

    inline void update(int &k,int last,int l,int r,int x){
//         if(l==r){deep[k]++;return;}
//         int mid=(l+r)>>1;
//         if(x<=mid) update(p[k].l,p[last].l,l,mid,x);
//         else update(p[k].r,p[last].r,mid+1,r,x);
        k=new_node();p[k]=p[last];
        if(l==r){fa[k]=fa[last];deep[k]=deep[last]+1;return;}
        int mid=(l+r)>>1;
        if(x<=mid) update(p[k].l,p[last].l,l,mid,x);
        else update(p[k].r,p[last].r,mid+1,r,x);
    }

在按秩合并中,如果两边深度相同,需要改变深度,这里有两种写法,第一种写法是直接找到对应点直接改,但是这样有个问题,就是这个对应点有可能属于其他版本,这样按秩合并复杂度就有错误,所以我们曹勇西面这个版本,本质还是可持久化数组的修改。洛谷上数据较弱,没能卡掉第一种写法。

这里 \(x\) 是数组下标。

1.8 合并两个集合

    inline bool merge(int &k,int a,int b){
        int faa=find(k,a),fab=find(k,b);
        if(fa[faa]==fa[fab]) return 0;
        if(deep[faa]>deep[fab]) swap(faa,fab);
        change(k,root[tail-1],1,n,fa[faa],fa[fab]);
        if(deep[faa]==deep[fab]) update(k,k,1,n,fa[fab]);
        return 1;
    }

这里需要注意,树上编号和数组下标不要弄混,其次就是注意 \(k\) 要加引用,因为在 change 中 \(k\) 改变了。

这里 \(faa,fab\) 都是树上编号,但是他们的 \(fa\) 都是他们对应的数组下标。

1.9 判断是否属于同一集合

    inline bool belong_to_the_same(int k,int a,int b){
        int faa=find(k,a),fab=find(k,b);
        return fa[faa]==fa[fab];
    }

比较简单,不作讲解,这里 \(a,b\) 都是数组下标。

2 总代码

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 200010
#define M 200010
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

int n,m;
struct node{
    int l,r;
};
int root[N],fa[N*40],deep[N*40],tail;
node p[N*40];


struct kcjbcj{
    int size;
    inline int new_node(){
        size++;p[size].l=p[size].r=0;return size;
    }
    inline void build(int &k,int l,int r){
        k=new_node();
        if(l==r){fa[k]=l;return;}
        int mid=(l+r)>>1;build(p[k].l,l,mid);build(p[k].r,mid+1,r);
    }
    inline int getposi(int k,int l,int r,int x){
        if(l==r) return k;
        int mid=(l+r)>>1;
        if(x<=mid) return getposi(p[k].l,l,mid,x);
        else return getposi(p[k].r,mid+1,r,x);
    }
    inline int find(int k,int x){
        int now=getposi(k,1,n,x);
        if(fa[now]==x) return now;
        else return find(k,fa[now]);
    }
    inline void change(int &k,int last,int l,int r,int x,int val){
        k=new_node();
        p[k]=p[last];
        if(l==r){deep[k]=deep[last];fa[k]=val;return;}
        int mid=(l+r)>>1;
        if(x<=mid) change(p[k].l,p[last].l,l,mid,x,val);
        else change(p[k].r,p[last].r,mid+1,r,x,val);
    }
    inline void update(int &k,int last,int l,int r,int x){
//         if(l==r){deep[k]++;return;}
//         int mid=(l+r)>>1;
//         if(x<=mid) update(p[k].l,p[last].l,l,mid,x);
//         else update(p[k].r,p[last].r,mid+1,r,x);
        k=new_node();p[k]=p[last];
        if(l==r){fa[k]=fa[last];deep[k]=deep[last]+1;return;}
        int mid=(l+r)>>1;
        if(x<=mid) update(p[k].l,p[last].l,l,mid,x);
        else update(p[k].r,p[last].r,mid+1,r,x);
    }
    
    inline bool merge(int &k,int a,int b){
        int faa=find(k,a),fab=find(k,b);
        if(fa[faa]==fa[fab]) return 0;
        if(deep[faa]>deep[fab]) swap(faa,fab);
        change(k,root[tail-1],1,n,fa[faa],fa[fab]);
        if(deep[faa]==deep[fab]) update(k,k,1,n,fa[fab]);
        return 1;
    }
    inline bool belong_to_the_same(int k,int a,int b){
        int faa=find(k,a),fab=find(k,b);
        return fa[faa]==fa[fab];
    }
};
kcjbcj bcj;

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);read(m);
    bcj.build(root[0],1,n);
    for(int i=1;i<=m;i++){
        int op;read(op);
        if(op==1){
            int a,b;read(a);read(b);tail++;root[tail]=root[tail-1];
            bcj.merge(root[tail],a,b);
        }
        else if(op==2){
            int k;read(k);tail++;
            root[tail]=root[k];
        }
        else if(op==3){
            int a,b;read(a);read(b);tail++;root[tail]=root[tail-1];
            printf("%d\n",(int)bcj.belong_to_the_same(root[tail],a,b));
        }
    }
    return 0;
}
上一篇:sql 触发器


下一篇:Codeforces Round #730 (Div. 2) A-D1