可持久化并查集

可持久化并查集,luogu 3402

#include<bits/stdc++.h>
#define rep(i,x,y) for(register int i=x;i<=y;i++)
#define dec(i,x,y) for(register int i=x;i>=y;i--)
#define ll long long
using namespace std;
const int N=301000;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*f;}
namespace zjc{
    int n,m,idx,dep[N<<5],fa[N<<5],ls[N<<5],rs[N<<5],root[N<<5];
     //一般主席树需要<<5大小,线段树需要<<2大小
    inline void build(int &k,int l,int r){
        k=++idx;
        if(l==r){fa[k]=l;return;}//下标是实际的父亲内映射 
        int mid=(l+r)>>1;
        build(ls[k],l,mid);
        build(rs[k],mid+1,r);
    }
    inline void merge(int &k,int kp,int l,int r,int p,int FA){
        k=++idx;
        ls[k]=ls[kp],rs[k]=rs[kp];
        if(l==r){fa[k]=FA;dep[k]=dep[kp];return;}
        int mid=(l+r)>>1;
        if(p<=mid) merge(ls[k],ls[kp],l,mid,p,FA);
        else merge(rs[k],rs[kp],mid+1,r,p,FA);
    }
    inline void change(int k,int l,int r,int p){
        //本身这个操作的意义在于修改已经在merge过程中建立的节点的dep值
        //所有不需要取地址 
        if(l==r){dep[k]++;return;}
        int mid=(l+r)>>1;
        if(p<=mid) change(ls[k],l,mid,p);
        else change(rs[k],mid+1,r,p);
    }
    //可持久化并查集下的自身实际上是某一编号对应的
    //线段树下标!!!(实际上就是线段树中的k)
    //而在fa数组下存储的值实际上是真实的点,
    int query(int k,int l,int r,int p){
        if(l==r) return k;
        int mid=(l+r)>>1;
        if(p<=mid) return query(ls[k],l,mid,p);
        else return query(rs[k],mid+1,r,p);
    }
    int find(int k,int p){
        //k代表历史版本 
        //find代表在某一历史版本中找到父亲
        //由于没有路径压缩实际上就是暴力跳
        
        //find函数本身就是不加优化的并查集的操作
        //query是由于将自身放入主席树维护导致增加的查找映射下标操作 
        //实际上最终存储的值仍然是正常父亲 
        int now=query(k,1,n,p);
        if(fa[now]==p) return now;
        return find(k,fa[now]);
    }
    void work(){
        n=read(),m=read();
        build(root[0],1,n);
        rep(i,1,m){
            int opt,x,y;
            opt=read(),x=read();
            if(opt==1){
                y=read();
                root[i]=root[i-1];
                int xx=find(root[i],x),yy=find(root[i],y);
                if(fa[xx]!=fa[yy]){
                    if(dep[xx]>dep[yy]) swap(xx,yy);
                    //将深度大的放在y上,完成fa[fa[xx]]=fa[yy](由于不是路径压缩,千万不敢写成fa[xx]=yy)
                    //通俗理解为,我爸爸也是你爸爸的儿子,你是我叔叔 
                    merge(root[i],root[i-1],1,n,fa[xx],fa[yy]);
                    if(dep[xx]==dep[yy]) change(root[i],1,n,fa[yy]);
                    //实质上就是按秩合并,但是具体操作在主席树上进行了实现 
                    /*void init(int n){
                    for(int i=0;i<n;i++)
                    fa[i]=i,rank[i]=0;
                    }
                    void unionn(int x,int y){
                        xx=find(x);
                        yy=find(y);
                        if(xx==yy) return;
                        if(rank[x]<rank[y])
                           fa[x]=y;//秩小的父亲是秩大的,使得树的总高较小
                        else if(x==y){
                           fa[y]=x;rank[x]++;} 
                        else fa[y]=x;
                    }*/ 
                }
            }
            if(opt==2) root[i]=root[x];
            if(opt==3){
                y=read();
                root[i]=root[i-1];
                int xx=find(root[i],x),yy=find(root[i],y);
                if(fa[xx]==fa[yy]) puts("1");
                else puts("0");
            }
        }return;
    }
}
int main(){
    zjc::work();
    return 0;
}

 

posted @ 2018-09-12 14:22  ASDIC减除  阅读(132)  评论(0编辑  收藏  举报