P3402 可持久化并查集

题意:

给定n,表示有n个集合,m次操作,操作有三种:
(1,a,b):合并集合a和集合b
(2,k):回到第k次操作的状态
(3,a,b):查询a和b是否在同一集合

数据范围:n<=1e5,m<=2e5

解法:

将数组可持久化(用可持久化线段树实现),维护并查集每个节点的父节点pre和深度dep。
合并是按秩合并。

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e5+5;
int lc[maxm*40],rc[maxm*40];
int pre[maxm*40],dep[maxm*40];
int rt[maxm<<1],tot;
int n,m;
void build(int l,int r,int &k){
    k=++tot;
    if(l==r){
        pre[k]=l;
        dep[k]=0;
        return ;
    }
    int mid=(l+r)/2;
    build(l,mid,lc[k]);
    build(mid+1,r,rc[k]);
}
void update(int x,int val,int l,int r,int last,int &k){
    k=++tot;
    lc[k]=lc[last],rc[k]=rc[last];
    if(l==r){
        pre[k]=val;
        dep[k]=dep[last];
        return ;
    }
    int mid=(l+r)/2;
    if(x<=mid)update(x,val,l,mid,lc[last],lc[k]);
    else update(x,val,mid+1,r,rc[last],rc[k]);
}
int ask(int x,int l,int r,int k){//查找某版本下x的父亲
    if(l==r)return pre[k];
    int mid=(l+r)/2;
    if(x<=mid)return ask(x,l,mid,lc[k]);
    else return ask(x,mid+1,r,rc[k]);
}
void add(int x,int l,int r,int k){//将某版本下x的深度+1
    if(l==r){
        dep[k]++;
        return ;
    }
    int mid=(l+r)/2;
    if(x<=mid)add(x,l,mid,lc[k]);
    else add(x,mid+1,r,rc[k]);
}
int ffind(int x,int k){//查询某个版本下x的父亲
    int pre_x=ask(x,1,n,k);
    if(x==pre_x)return x;
    return ffind(pre_x,k);
}
signed main(){
    scanf("%d%d",&n,&m);
    build(1,n,rt[0]);
    for(int i=1;i<=m;i++){
        int op;scanf("%d",&op);
        if(op==1){//合并a,b
            int a,b;scanf("%d%d",&a,&b);
            rt[i]=rt[i-1];
            int x=ffind(a,rt[i]);
            int y=ffind(b,rt[i]);
            if(x==y)continue;
            if(dep[x]>dep[y])swap(x,y);//改成dep[x]<dep[y]
            update(x,y,1,n,rt[i-1],rt[i]);//把短的x接到长的y上
            if(dep[x]==dep[y])add(y,1,n,rt[i]);//如果dep[x]=dep[y],dep[y]需要+1
        }else if(op==2){//将版本置为rt[x]
            int x;scanf("%d",&x);
            rt[i]=rt[x];
        }else if(op==3){//判断a,b是否在同一个集合
            int a,b;scanf("%d%d",&a,&b);
            rt[i]=rt[i-1];
            int x=ffind(a,rt[i]);
            int y=ffind(b,rt[i]);
            if(x==y)puts("1");
            else puts("0");
        }
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值