1. 程式人生 > >可持久化並查集(外傳)——[按秩啟發式合併]

可持久化並查集(外傳)——[按秩啟發式合併]

重新開坑

奉上最近覺得的神作(至少從小說與這首曲子來說是這樣的)。

之前寫到過可持久化並查集三部曲,現在想來,唯獨沒有提到按秩合併,在研習了啟發式合併後,決定重新為並查集寫一份外傳,記錄並查集的另一作用。

什麼是按秩合併,就小編來看,其實就是啟發式合併的一種,在滿足不路徑壓縮改變fa[x]的前提下,能夠使得並查集的深度為logn級別的。

但是提醒,這裡的按秩合併與路徑壓縮並不矛盾,按秩合併關鍵在union函式,而路徑壓縮是在find函式中,若只是普通並查集,就只需要上路徑壓縮就可以了,應該沒有哪個喪盡天良的出題人為了卡路徑壓縮要求兩個優化都用才能AC。。。。

如何實現,先上程式碼。

int fa[N],f[N];
int siz[N],dpn[N];  
int cnt_merge = 0;  
int find(int x){  
    if(fa[x]==x)return x;  
    int k=findfather(fa[x]);  
    dpn[x]=dpn[fa[x]]+1;//合併時以類似求字首的方式維護dfn中的值。  
    return k;  
}
inline void merge(int x,int y){  //uniona函式,按秩合併 
    x=find(x),y=find(y);
    if(x==y)return void(++cnt_merge);  
    if
(siz[x]>siz[y])fa[y]=x,f[y]=++cnt_merge,siz[x]+= siz[y]; else fth[x]=y,f[x]=++cnt_merge,siz[y]+=siz[x]; }

這樣寫有什麼用呢,或者說為什麼這麼寫?
在兩個不同子集連線時,如果我們按照rank來連線,把rank低的連在rank高的下面,讓rank高的做father,那麼我們就相當於維護了一棵樹,保證了類似於啟發式合併一樣的樹高最壞logn

神奇吧,來一道題清爽一下。

//by anantheparty
#include<cstdio>
const int
N=5e5+5; int f[N],d[N],r[N],p[N]; int find(int i){ if(p[i]==i)return i; register int j=find(p[i]); d[i]=d[p[i]]+1; return j; } int unionn(int i,int j){ i=find(i),j=find(j); if(i==j)return 0; if(r[i]==r[j])++r[j]; if(r[i]<r[j]){ p[i]=j; return i; } p[j]=i; return j; } int query(int i,int j){ register int s=0; if(find(i)==find(j)) while(i!=j) if(d[i]>d[j])s<f[i]?s=f[i]:0,i=p[i]; else s<f[j]?s=f[j]:0,j=p[j]; return s; } inline void read(int &res){ static char ch; while((ch=getchar())<'0'||ch>'9');res=ch-48; while((ch=getchar())>='0'&&ch<='9')res=res*10+ch-48; } int main(){ register int n,m,k,s,t,last=0; read(n),read(m); for(;n;--n)p[n]=n; while(m--){ read(k),read(s),read(t); s^=last,t^=last; if(k)printf("%d\n",last=query(s,t)); else f[unionn(s,t)]=++n; } return 0; }

哈哈我可沒有忘記並查集系列的看板娘

這裡寫圖片描述