可持久化並查集

可持久化並查集

洛谷模板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.
相關文章
相關標籤/搜索