1. 程式人生 > >可持久化專題(三)——可持久化並查集

可持久化專題(三)——可持久化並查集

前言

要學可持久化並查集,必須先會可持久化陣列

簡介

可持久化並查集應該是一個挺實用的資料結構(例如NOI2018Day1T1中就有它的身影)。

它主要建立於可持久化陣列的基礎之上(而可持久化陣列的實現是完全基於主席樹的),因為這樣就可以去訪問一些歷史版本從而實現可持久化了。

按秩合併

由於可持久化的緣故,我們要切記,可持久化並查集是不能像普通並查集一樣寫路徑壓縮的!

或許有人會問,不路徑壓縮,還不TT飛?

沒關係,沒有路徑壓縮,我們還有按秩合併,它的複雜度均攤是O(logn)O(logn)的,平時由於已經有路徑壓縮了,
所以基本上沒人寫按秩合併(實在沒什麼必要,時間複雜度優化並不大),而此時此刻,沒法用路徑壓縮,按秩合併就起了很大的作用。

LinkLink

按秩合併的複雜度證明詳見部落格啟發式合併

具體實現

如果對可持久化並查集還有什麼不懂的,最好再多思考一下,畢竟這還是有點深奧的。

下面是洛谷上可持久化並查集板子題的程式碼:(程式碼比可持久化陣列長了許多)

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define LL long long
#define swap(x,y) (x^y?(x^=y,y^=x,x^=y):0)
#define tc() (A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++)
#define pc(ch) (pp_<100000?pp[pp_++]=(ch):(fwrite(pp,1,100000,stdout),pp[(pp_=0)++]=(ch))) #define N 200000 int pp_=0;char ff[100000],*A=ff,*B=ff,pp[100000]; using namespace std; int n,Q,tot=0,rt[N+5],a[N+5]; struct Chairman_Tree { int Son[2],fa,level; }node[N*20]; inline void read(int &x) { x=0;int f=1;char ch;
while(!isdigit(ch=tc())) f=ch^'-'?1:-1; while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc())); x*=f; } inline void write(int x) { if(x<0) pc('-'),x=-x; if(x>9) write(x/10); pc(x%10+'0'); } inline void Build(int &rt,int l,int r)//初始的建樹,一開始每個節點的fa都是本身,這是並查集的基礎思想 { rt=++tot; int mid=l+r>>1; if(!(l^r)) {node[rt].fa=l;return;} Build(node[rt].Son[0],l,mid),Build(node[rt].Son[1],mid+1,r); } inline void NewPoint(int &rt,int lst,int l,int r,int x,int fa)//新插入一個節點 { rt=++tot; int mid=l+r>>1; if(!(l^r)) {node[rt].fa=fa,node[rt].level=node[lst].level;return;}//更新fa,並複製以前版本的這個節點的level node[rt].Son[0]=node[lst].Son[0],node[rt].Son[1]=node[lst].Son[1]; if(x<=mid) NewPoint(node[rt].Son[0],node[lst].Son[0],l,mid,x,fa); else NewPoint(node[rt].Son[1],node[lst].Son[1],mid+1,r,x,fa); } inline void Add_level(int rt,int l,int r,int x)//增加一個節點的在按秩合併時的優先順序 { int mid=l+r>>1; if(!(l^r)) {++node[rt].level;return;} if(x<=mid) Add_level(node[rt].Son[0],l,mid,x); else Add_level(node[rt].Son[1],mid+1,r,x); } inline int Query(int rt,int l,int r,int x)//詢問x節點在某一版本下的位置 { int mid=l+r>>1; if(!(l^r)) return rt; if(x<=mid) return Query(node[rt].Son[0],l,mid,x); else return Query(node[rt].Son[1],mid+1,r,x); } inline int getfa(int rt,int x)//詢問x節點在某一版本下的祖先 { int fa=Query(rt,1,n,x); return node[fa].fa^x?getfa(rt,node[fa].fa):fa;//如果x節點在該版本下的父親等於它本身,就返回x,否則返回x的父親在這個版本下的祖先,和經典的getfa()函式差不多 } inline void connect(int v,int x,int y)//在版本v中連線x和y,將他們放入一個集合中 { int fx=getfa(rt[v],x),fy=getfa(rt[v],y);//先求出版本v中它們的祖先 if(!(fx^fy)) return;//如果祖先相同,就退出函式 if(node[fx].level<node[fy].level) swap(fx,fy);//如果x的優先順序小於y的優先順序,就交換x和y NewPoint(rt[v],rt[v-1],1,n,node[fy].fa,node[fx].fa);//將優先順序小的節點的父親連向優先順序大的節點的父親 if(!(node[fx].level^node[fy].level)) Add_level(rt[v],1,n,node[fx].fa);//如果它們的優先順序相同,就將它們合併後的祖宗的優先順序加1 } int main() { register int i; for(read(n),read(Q),Build(rt[0],i=1,n);i<=Q;++i)//先建一棵樹,然後進行操作 { int op,x,y;read(op),read(x); if(op^2) read(y),rt[i]=rt[i-1]; switch(op) { case 1:connect(i,x,y);break;//在當前版本下連線x和y case 2:rt[i]=rt[x];break;//將當前版本還原回曾經的版本x case 3:pc(getfa(rt[i],x)^getfa(rt[i],y)?'0':'1'),pc('\n');break;//若當前版本下x和y的父親相同,輸出1,否則輸出0 } } return fwrite(pp,1,pp_,stdout),0; }