zoukankan      html  css  js  c++  java
  • Yangk's-可持久化并查集

    可持久化并查集

    前置:

    %%%AgOH的B站:https://space.bilibili.com/120174936/
    什么是可持久化并查集呢?当然是 并查集+可持久化(主席树)好像废话
    害,当然不懂这两个知识的建议先去看看,简单学习一下
    我在这就简单的回忆一下

    并查集

    开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。
    关于他的优化,常见的是路径压缩,但我们今天要用的是按秩合并
    啥是按秩合并,简单来说:把深度小的并到深度大的上,保证深度不会增加

    主席树

    主席树是一种可持久化的数据结构一可持久化线段树,保存树的历史版本,可以随时访问前某个版本的值
    可持久化数组呢?主席树维护的不是一个权值线段树吗,可持久化数组维护的就是一颗普通线段树,主席树用root[]和hjt[]来维护某些东西,可持久化数组就是用他俩维护一个数组
    也就是 可持久化数组的那颗线段树的叶子节点们组成的线性的数组

    那把他们合在一起呢?

    P3402 可持久化并查集

    没错,操作2,回到第几次操作,也就是我们要用到更新被覆盖掉的版本,和主席树的想法一样,

    可持久化并查集

    是一种可以支持回退,访问之前版本的并查集
    那我们怎么让并查集可持久化呢?
    来想想最基础的并查集

    int fa[maxn];
    int find(int x)
    {
        return fa[x]==x?x:find(x);
    }
    void merge(int x,int y)
    {
        fa[find(x)]=find(y);
    }
    

    这个并查集的核心是什么?

    递归查找?显然不是,因为我们路径压缩优化时就把find函数改掉了
    对,fa数组,并查集的核心就是找人做他的父亲,我们只需要把fa数组可持久化掉,这个并查集就可持久化了

    可持久化并查集的优化要用按秩合并

    为什么不路径压缩了呢?

    每次循环,只要没有满足条件,fa数组就会有一个位置被修改
    对于普通数组来说,这修改完全没问题,他是建立在主席树上的
    可持久化数组每进行一次单点修改就会多一个新的版本存放新的结点。小数据暂且无妨,但是大数据妥妥MLE
    所以路径压缩就不要使用了
    但不要忘了按秩合并
    因为每个版本的并查集的结点深度可能是不一样的,所以我们还需要要新开一个可持久化数组来记录每个版本的dep数组

    总结

    用两个可持久化数组分别维护并查集的fa数组(每个集合的根结点)和dep数组(每个结点的深度)
    并查集要按秩合并,不要路径压缩

    然后我们来拆解代码

    准备工作:
    数组开40倍,rootfa[],rootdep[],cnt内存池,tot初始化计数器,全局变量n

    const int maxn = 2e5+9;
    int tot,cnt,rootfa[maxn],rootdep[maxn],n;
    struct node 
    {
        int l,r,val;
    }hjt[maxn*40*2];
    

    主函数:
    建树:因为rootfa是有初值的,每个人的父亲都是他自己嘛,rootdep就不需要了,初始所有节点深度都是0

          build(1,n,rootfa[0]);
    

    第一个操作 合并两个集合
    第二个操作 回到第k个版本
    怎么回到呢,把让当前版本根节点对应的hjt数组编号赋给当前版本就好了,不用重新复制,这样合并和查询时都是在原来的树上操作

          else if(op==2)
          {
              scanf("%d",&k);
              rootfa[var]=rootfa[k];
              rootdep[var]=rootdep[k];
          }
    

    第三个操作 询问a,b是不是在一个集合里
    因为查询也算一次操作,所以我们让当前版本根节点对应的hjt数组编号和上一次相等,再并查集递归找爹

            else 
            {
                scanf("%d%d",&a,&b);
                rootfa[var]=rootfa[var-1];
                rootdep[var]=rootdep[var-1];
                int x=find(var,a);
                int y=find(var,b);
                if(x==y) puts("1");
                else puts("0");
            }
    

    建树 build:
    因为now的值被修改所以传引用
    初始化fa[],自己的父亲是自己,如果到了叶子节点 hjt[now]=++tot;

    void build(int l,int r,int &now)
    {
        now=++cnt;
        if(l==r) 
        {
            hjt[now].val=++tot;
            return ;
        }
        int mid=(l+r)>>1;
        build(l,mid,hjt[now].l);
        build(mid+1,r,hjt[now].r);
    }
    

    修改 modify:
    void modify(左边界,右边界,历史版本,新版本,哪个位置,修改成什么)
    其实和可持久化数组几乎一样

    void modify(int l,int r,int var,int &now,int pos,int num)
    {
        now=++cnt;
        hjt[now]=hjt[var];
        if(l==r) 
        {
            hjt[now].val=num;
            return ;
        }
        int mid=(l+r)>>1;
        if(pos<=mid) modify(l,mid,hjt[var].l,hjt[now].l,pos,num);
        else modify(mid+1,r,hjt[var].r,hjt[now].r,pos,num);
    }
    

    查询 query:
    int query(左边界,右边界,哪个版本,哪个位置)
    和可持久化数组几乎一样(坚信脸)

    int query(int l,int r,int now,int pos)
    {
        if(l==r)  return hjt[now].val;
        int mid=(l+r)>>1;
        if(pos<=mid) return query(l,mid,hjt[now].l,pos);
        else return query(mid+1,r,hjt[now].r,pos);
    }
    

    递归找爹 find :
    只不过找父亲不能fa[x]一步到位了,要用query

    int find(int var,int x)
    {
        int fx=query(1,n,rootfa[var],x);
        return fx==x?x:find(var,fx);
    }
    

    按秩合并优化 merge :
    先找他们的父亲,如果是一个爹,那不用合并
    如果不是,找到他俩的深度,把深度小的合并到大的上面,尽量保证深度不会增加
    如果一样深,那么随便放,但深度要+1

    void merge (int var,int x,int y)
    {
        x=find(var-1,x);
        y=find(var-1,y);
        if(x==y) 
        {
            rootfa[var]=rootfa[var-1];
            rootdep[var]=rootfa[var-1];
        }
        else 
        {
            int dpx=query(1,n,rootdep[var-1],x);
            int dpy=query(1,n,rootdep[var-1],y);
            if(dpx<dpy)
            {
                modify(1,n,rootfa[var-1],rootfa[var],x,y);
                rootdep[var]=rootdep[var-1];
            }
            else if(dpx>dpy)
            {
                modify(1,n,rootfa[var-1],rootfa[var],y,x);
                rootdep[var]=rootdep[var-1];
            }
            else 
            {
                modify(1,n,rootfa[var-1],rootfa[var],x,y);
                modify(1,n,rootdep[var-1],rootdep[var],y,dpy+1);
            }
        }
    }
    

    然后就没有啦,是不是很简单呢?
    有关于并查集的板子在这里

    完整代码

    #include <bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define lowbit(a) ((a)&-(a))
    #define clean(a,b) memset(a,b,sizeof(a))
    const int mod = 1e9 + 7;
    const int inf=0x3f3f3f3f;
      
    int _;
    //==================================================================
    const int maxn = 2e5+5;
    int a[maxn],n;
    struct Node
    {
        int l,r,val;
    }hjt[maxn*40*2];
    int cnt,rootfa[maxn],rootdep[maxn],tot;
    void build(int l,int r,int &now)
    {
        now=++cnt;
        if(l==r)
        {
            hjt[now].val=++tot;
            return;
        }
        int m = (l+r)>>1;
        build(l,m,hjt[now].l);
        build(m+1,r,hjt[now].r);
    }
    void modify(int l,int r,int ver,int &now,int pos,int num)
    {
        hjt[now=++cnt]=hjt[ver];
        if(l==r)
        {
            hjt[now].val=num;
            return;
        }
        int m = (l+r)>>1;
        if(pos<=m) modify(l,m,hjt[ver].l,hjt[now].l,pos,num);
        else modify(m+1,r,hjt[ver].r,hjt[now].r,pos,num);
    }
    int query(int l,int r,int now,int pos)
    {
        if(l==r) return hjt[now].val;
        int m = (l+r)>>1;
        if(pos<=m) return query(l,m,hjt[now].l,pos);
        else return query(m+1,r,hjt[now].r,pos);
    }
    int find(int var,int x)
    {
        int fx=query(1,n,rootfa[var],x);
        return fx==x?x:find(var,fx);
    }
    void merge(int var,int x,int y)
    {
        x=find(var-1,x);
        y=find(var-1,y);
        if(x==y) 
        {
            rootfa[var]=rootfa[var-1];
            rootdep[var]=rootdep[var-1];
        }
        else 
        {
            int dpx=query(1,n,rootdep[var-1],x);
            int dpy=query(1,n,rootdep[var-1],y);
            if(dpx<dpy)
            {
                modify(1,n,rootfa[var-1],rootfa[var],x,y);
                rootdep[var]=rootdep[var-1];
            }
            else if (dpx>dpy)
            {
                modify(1,n,rootfa[var-1],rootfa[var],y,x);
                rootdep[var]=rootdep[var-1];
            }
            else 
            {
                modify(1,n,rootfa[var-1],rootfa[var],x,y);
                modify(1,n,rootdep[var-1],rootdep[var],y,dpy+1);
            }
        }
    }
    int main()
    {
        //======================================
        int m;
        scanf("%d%d",&n,&m);
        build(1,n,rootfa[0]);
        for(int i=1;i<=m;i++)
        {
            int opt,k,a,b;
            scanf("%d",&opt);
            if(opt==1) 
            {
                scanf("%d%d",&a,&b);
                merge(i,a,b);
            }
            else if(opt==2)
            {
                scanf("%d",&k);
                rootfa[i]=rootfa[k];
                rootdep[i]=rootdep[k];
            }
            else 
            {
                scanf("%d%d",&a,&b);
                rootfa[i]=rootfa[i-1];
                rootdep[i]=rootdep[i-1];
                int x=find(i,a);
                int y=find(i,b);
                if(x==y) puts("1");
                else puts("0");
            }
        }
        return 0;
    }
    
  • 相关阅读:
    Git常用命令
    Shell脚本学习
    Shell脚本学习
    Shell脚本学习
    Git ignore文件的用法
    RSA非对称加密算法
    C++ 标准库中的堆(heap)
    EM(Entity FrameWork)- code first , using in Visual stdio 2017
    C# 图片文字识别
    C# 调 C++ DLL 托管代码中释放非托管函数分配的内存
  • 原文地址:https://www.cnblogs.com/YangKun-/p/12920394.html
Copyright © 2011-2022 走看看