可持久化并查集

可持久化并查集

洛谷模板c++

前言

  • 听名字像是一个十分高端的东西,在今年NOI2018以前,我从未想过本身会用这个数据结构
  • 然而,当发现Day1 T1用可持久化并查集能够暴力A的时候,心中无尽的无奈......(毕竟不会)
  • 考完后了解了一下,发现彷佛是一个挺好理解的数据结构。
  • 因此就写了这篇学习笔记!

前置技能

  • 可持久化并查集,所须要知道的前置技能很显然!
  • 顾名思义,可持久化并查集=可持久化+并查集=可持久化数组+并查集=主席树+并查集!
  • 所以,咱们首先要会主席树和并查集。
  • 可持久化数组这个没什么好说的,就那几个操做,详情见洛谷可持久化数组模板
  • 并查集却是要提一下!
  • 并查集中有几种合并方式:
  • 一种是直接暴力连父亲(这显然用不上)
  • 一种是路径压缩的合并(这个在普通并查集中很经常使用,可是好像没法在可持久化并查集中用,据说是能够构造数据使可持久化并查集的空间爆掉?);
  • 还有一种是按秩合并,也就是可持久化并查集中经常使用的合并方式!其实也就是一种相似于启发式合并的方式,每一次合并时选择一个深度小的点向深度大的合并。这样就能够保证并查集的高度不会增加的太快,保证高度尽可能均衡。

步入正题——可持久化并查集

  • 其实咱们能够发现看懂了前置技能后,可持久化并查集已经不难实现。
  • 可持久化并查集其实就是指的用可持久化数组维护并查集中的\(Fa\)与按秩合并所须要的\(dep\)
  • 所谓可持久化并查集,能够进行的操做就只有几个:
  1. 回到历史版本(否则怎么叫可持久化呢2333)
  2. 合并两个集合(毕竟仍是个并查集么)
  3. 查询节点所在集合的祖先,固然,所以也能够判断是否在同一个集合中!
  • 对于1操做,咱们能够很轻松的利用可持久化数组实现。就直接把当前版本的根节点定为第k个版本的根节点就好了!
  • 至于代码实现?
root[i]=root[x];
//是否是很简单呀!
  • 对于2操做,其实也就是按照我在前置技能中所说的按秩合并!
  • 对于3操做,也就是在可持久化数组中查询!
  • 这样说确定会有点懵圈,不如一个个函数的解释!
#define Mid ((l+r)>>1)
#define lson L[rt],l,Mid
#define rson R[rt],Mid+1,r
// 整个代码的三个宏定义

初始化建树

void build(int &rt,int l,int r)
    {
        rt=++cnt;
        if(l==r){fa[rt]=l;return ;}
        build(lson);build(rson);
    }
    // 就是普通的可持久化数组构建法,不过维护的是Fa而已

合并

void merge(int last,int &rt,int l,int r,int pos,int Fa)
    {
        rt=++cnt;L[rt]=L[last],R[rt]=R[last]; 
        if(l==r)
        {
            fa[rt]=Fa;
            dep[rt]=dep[last];//继承上个版本的值
            return ;
        }
        if(pos<=Mid)merge(L[last],lson,pos,Fa);
        else merge(R[last],rson,pos,Fa);
    }
    // 这个就是单纯的将一个点合并到另外一个点上的可持久化数组操做!

修改节点深度(方便按秩合并)

void update(int rt,int l,int r,int pos)
    {
        if(l==r){dep[rt]++;return ;}
        if(pos<=Mid)update(lson,pos);
        else update(rson,pos);
    }
    // 可持久化数组普通操做
    // 可能有人会问为何修改节点深度的时候不须要新开节点!
    // 其实新开节点是根据咱们的须要来的!
    // 若是咱们须要某个值在某个版本的信息,那么,每当这个值进行修改的时候,咱们都须要新添加一个节点,使得咱们能够查到各个版本的值
    // 然而dep咱们并不须要知道它之前的值是多少,咱们只须要用它当前的值去合并就好了!

查询某一个值所在可持久化数组中的下标

int query(int rt,int l,int r,int pos)
    {
        if(l==r)return rt;
        if(pos<=Mid)return query(lson,pos);
        else return query(rson,pos);
    }
    // 为了找祖先的操做

查找祖先

int find(int rt,int pos)
    {
        int now=query(rt,1,n,pos);
        if(fa[now]==pos)return now;
        return find(rt,fa[now]);
    }
    // 暴力找祖先
  • 以上操做就是可持久化并查集的基础函数 单次操做复杂度均为\(log\)级的,空间须要注意,也要开\(n*log\)级,通常就正常空间乘上\(40\)左右吧。
  • 合并与查询操做就和普通并查集差很少,只是须要注意当前查询的版本是什么就能够了。
  • 固然还须要注意一点,每一次操做都要先把上个版本给传递过来\(root[i]=root[i-1]\)
  • 放个代码看看吧!
  • 按秩合并
posx=find(root[i],x);posy=find(root[i],y);
    if(fa[posx]!=fa[posy])
    {
        if(dep[posx]>dep[posy])swap(posx,posy);
        merge(root[i-1],root[i],1,n,fa[posx],fa[posy]);
        if(dep[posx]==dep[posy])update(root[i],1,n,fa[posy]);
        // 由于不可能出现深度相同的两个点,因此要把其中一个点深度+1,因为是深度小的合到深度大的上,因此把深度小的增长深度
    }
  • 查找
posx=find(root[i],x);posy=find(root[i],y);
    if(fa[posx]==fa[posy])puts("1");
    else puts("0");
    // 这个真和普通并查集没区别,只是须要注意是什么版本的并查集...
  • 至此,可持久化并查集的全部操做就已经解释完了!(相信聪明的你必定能够实现它)

其实,把上面的操做拼起来就是完整代码,不过我仍是粘一个完整版吧!git

#include<bits/stdc++.h>
#define N 301000
using namespace std;
template<typename T>inline void read(T &x)
{
    x=0;
    static int p;p=1;
    static char c;c=getchar();
    while(!isdigit(c)){if(c=='-')p=-1;c=getchar();}
    while(isdigit(c)) {x=(x<<1)+(x<<3)+(c-48);c=getchar();}
    x*=p;
}
int n,m;
int L[N*30],R[N*30],fa[N*30],dep[N*30];
int root[N*30];
namespace Persistant_Union_Set
{
#define Mid ((l+r)>>1)
#define lson L[rt],l,Mid
#define rson R[rt],Mid+1,r
    int cnt;
    void build(int &rt,int l,int r)
    {
        rt=++cnt;
        if(l==r){fa[rt]=l;return ;}
        build(lson);build(rson);
    }
    void merge(int last,int &rt,int l,int r,int pos,int Fa)
    {
        rt=++cnt;L[rt]=L[last],R[rt]=R[last];
        if(l==r)
        {
            fa[rt]=Fa;
            dep[rt]=dep[last];
            return ;
        }
        if(pos<=Mid)merge(L[last],lson,pos,Fa);
        else merge(R[last],rson,pos,Fa);
    }
    void update(int rt,int l,int r,int pos)
    {
        if(l==r){dep[rt]++;return ;}
        if(pos<=Mid)update(lson,pos);
        else update(rson,pos);
    }
    int query(int rt,int l,int r,int pos)
    {
        if(l==r)return rt;
        if(pos<=Mid)return query(lson,pos);
        else return query(rson,pos);
    }
    int find(int rt,int pos)
    {
        int now=query(rt,1,n,pos);
        if(fa[now]==pos)return now;
        return find(rt,fa[now]);
    }
#undef Mid
#undef lson
#undef rson
}
using namespace Persistant_Union_Set;
int main()
{
    read(n);read(m);
    build(root[0],1,n);
    for(int i=1;i<=m;i++)
    {
        static int opt,x,y;
        read(opt);read(x);
        if(opt==1)
        {
            read(y);
            static int posx,posy;
            root[i]=root[i-1];
            posx=find(root[i],x);posy=find(root[i],y);
            if(fa[posx]!=fa[posy])
            {
                if(dep[posx]>dep[posy])swap(posx,posy);
                merge(root[i-1],root[i],1,n,fa[posx],fa[posy]);
                if(dep[posx]==dep[posy])update(root[i],1,n,fa[posy]);
            }
        }
        else if(opt==2)root[i]=root[x];
        else if(opt==3)
        {
            read(y);
            root[i]=root[i-1];
            static int posx,posy;
            posx=find(root[i],x);posy=find(root[i],y);
            if(fa[posx]==fa[posy])puts("1");
            else puts("0");
        }
    }
    return 0;
}

扩展——可持久化带权并查集

  • 感受这个比普通的带权并查集直接一些!
  • 直接在可持久化数组里维护,即在合并父亲的时候同时维护权值的信息就好了!
  • (是否是特别的简单呢OVO )

题目

  • 可持久化并查集的题目我还真没作过几个,毕竟这个东西只要板子会打,剩下的都是思惟的事情了,代码实现的难度并不高。题目好像也没有几个。
  • 洛谷的模板题能够打一下,练一练板子。(之后好复制)
  • 若是实在想练一下,那么就去把noi2018归程用可持久化并查集给作掉233.
相关文章
相关标签/搜索