可持久化並查集


可持久化並查集

洛谷模板

前言

  • 聽名字像是一個十分高端的東西,在今年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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM