• IT发烧友,一个真正的技术交流群


    可持久化并查集

    洛谷模板

    前言

    • 听名字像是一个十分高端的东西,在今年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");
    	// 这个真和普通并查集没区别,只是需要注意是什么版本的并查集...
    
    • 至此,可持久化并查集的所有操作就已经解释完了!(相信聪明的你一定可以实现它)

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

    #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.
  • 相关阅读:
    网页制作-表单元素2
    网页制作-表单元素
    网页制作_表格
    网页制作常用标签
    IT新起之秀
    Android Studio 快捷键
    android github
    手机抓包 http tcp udp?
    Ubuntu16.04 Caffe 安装步骤记录(超详尽)(转载)
    ubuntu16.04 在cuda9.0环境下编译安装opencv2.4.13.7
  • 原文地址:https://www.cnblogs.com/peng-ym/p/9357220.html
Copyright © 2020-2023  润新知