可持久化并查集学习笔记

这个其实和可持久化线段树关系很大,算是比较具体的应用了

维护一个普通的并查集,我们只需要一个fa数组,然后每次Find的时候路径压缩,简短方便

但是当需要维护历史版本的时候,就有一些区别了

模板题:洛谷 P3402 

假设我们先不进行路径压缩,而是简单的构造一个裸的并查集,那么问题就变成维护有历史版本的fa数组了:这是可持久化数组的裸题

问题就在于,可持久化线段树的优秀的复杂度有一个比较严格的要求:每次修改只能是点修改

而我们考虑路径压缩的过程,发现我们在一次Find操作的同时,被改变fa值的元素可能不止一个,例如下图

如果我们在黑色的Union关系的基础上添加一个红色的关系,可以发现5->4的路径是有待压缩成5->1的

如果多次合并后,可能会出现比较可怕的情形:

树有可能退化成一条链(因为我们连边的方法确定),在某次Find叶子节点的时候,可能一下路径压缩了好几次

一开始我觉得好像也不会有什么问题:路径压缩的次数跟Union的次数一样

但是仔细一想,如果不断回到这个版本连续Find,一共路径压缩的次数是N2的,在时间空间上都是无法接受的

而在普通的并查集中,路径压缩的正确性在于,压缩后是一劳永逸的 这就是可持久化问题的痛苦之处

现在我们终于想起了另一种降低并查集层数的方法了:按秩合并,即根据 以当前节点为根的子树 的深度进行有方向的连边

比如,如果以a为根的子树深度为d+1、以b为根的子树深度为d+2,那么连一条a->b的边,整体的深度仍为d+2

这样就跟AVL树在原理上很类似了,整体上树的深度是logN级别的

我们可以在记录fa数组的同时,引入lv数组来记录深度,这样一来,每次Union的时候,我们仅会进行一次连边,对应着可持久化数组的一次点修改

【在此停顿】

事实上,我们在一次Union的时候不仅会进行一次fa上的点修改,同时被连边的点有可能会lv+1(当lv[a]==lv[b]的时候,不管如何都会改变被连边点的子树深度)

不过,可持久化数组的一个元素i 应该是包含l[i]、r[i]、fa[i]和lv[i]的一个struct,所以fa和lv上的修改仍可视为一次可持久化数组上的点修改,只不过需要拆分成两步

  1. 修改fa[x]
  2. 修改lv[x]

但是只有当这两步都完成的时候,才是Union后的历史版本

代码在Union的部分稍显丑陋...不知道怎么改进

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
using namespace std;

const int MAX=100005;

int n,m;

int tot,sz=1;
int root[MAX*2];
int l[MAX*60],r[MAX*60],fa[MAX*60],lv[MAX*60];

void Build()
{
    root[0]=1;
    while(sz<n)
        sz<<=1;
    
    for(int i=1;i<sz;i++)
        l[i]=(i<<1),r[i]=(i<<1)+1;
    for(int i=1;i<=n;i++)
        fa[i+sz-1]=i,lv[i+sz-1]=1;
    tot=(sz<<1)-1;
}

inline int Locate(int k,int p,int a,int b)
{
    if(a==b)
        return k;
    
    int mid=(a+b)>>1;
    if(p<=mid)
        return Locate(l[k],p,a,mid);//#1: Misuse k<<1 / (k<<1)+1 instead of l[k] / r[k]
    else
        return Locate(r[k],p,mid+1,b);
}

inline int Query(int x,int p)
{
    int f=fa[Locate(root[x],p,1,sz)];
    if(f==p)
        return p;
    return Query(x,f);
}

inline void Change(int idx,int k,int p,int a,int b)
{
    if(a==b)
        return;
    
    int mid=(a+b)>>1;
    if(p<=mid)
    {
        r[idx]=r[k];
        l[idx]=++tot;
        Change(l[idx],l[k],p,a,mid);
    }
    else
    {
        l[idx]=l[k];
        r[idx]=++tot;
        Change(r[idx],r[k],p,mid+1,b);
    }
}

inline void Union(int idx,int x,int a,int b)
{
    a=Query(x,a);
    b=Query(x,b);
    if(a==b)
    {
        root[idx]=root[x];
        return;
    }
    
    int lp=Locate(root[x],a,1,sz),rp=Locate(root[x],b,1,sz);
    int p=(lv[lp]>=lv[rp]?b:a),nlv=max(lv[lp],lv[rp])+(lv[lp]==lv[rp]);
    
    int tmp=++tot;
    Change(tmp,root[x],p,1,sz);
    fa[tot]=a+b-p,lv[tot]=min(lv[lp],lv[rp]);
    root[idx]=++tot;
    Change(tot,tmp,a+b-p,1,sz);
    fa[tot]=a+b-p,lv[tot]=nlv;
}

int main()
{
//    freopen("input.txt","r",stdin);
    scanf("%d%d",&n,&m);
    Build();
    
    int cur=0,cnt=0;
    for(int i=1;i<=m;i++)
    {
        int op,x,y;
        scanf("%d",&op);
        
        if(op==1)
        {
            cnt++;
            scanf("%d%d",&x,&y);
            Union(cnt,cur,x,y);
            cur=cnt;
        }
        if(op==2)
        {
            scanf("%d",&x);
            root[++cnt]=root[x];
            cur=x;
        }
        if(op==3)
        {
            scanf("%d%d",&x,&y);
            printf("%d\n",Query(cur,x)==Query(cur,y));
            root[++cnt]=root[cur];
            cur=cnt;
        }
    }
    return 0;
}
View Code

暂时不知道有什么实际用途,先学着就是了

(完)

猜你喜欢

转载自www.cnblogs.com/LiuRunky/p/Sustainable_UFS.html