淺談左偏樹
阿新 • • 發佈:2019-01-09
可並堆
可並堆顧名思義就是可以合併的堆。
這裡不講二項堆和斐波那契堆,只講左偏樹。
左偏樹
左偏樹顧名思義就是向左偏的樹。
給每個點定義一個\(dist\),滿足下面三個條件:
1、空結點的\(dist\)等於\(-1\)
2、每個結點的左兒子的\(dist\)都大於右兒子的\(dist\)
3、每個結點的\(dist\)都等於右兒子的\(dist+1\)
根據上面這些性質,我們可以推出左偏樹中根結點的\(dist\)最大不超過\(logsize\)。
合併
左偏樹合併非常簡單,假設我要合併\(a,b\)兩顆樹並且\(val_a<val_b\)(為了滿足小根堆性質,不滿足就交換\(a,b\)
如果\(a\)或\(b\)為空返回另一個結點
否則合併\(a\)的右兒子和\(b\),如果這個時候右兒子的\(dist\)大於左兒子的\(dist\)就交換\(a\)的左右兒子,更新\(a\)的\(dist\)然後返回\(a\)。
由於每一層遞迴的\(dist_a+dist_b\)都會比上一層小\(1\),最小可以到\(-1\),所以時間複雜度是\(O(logsize_a+logsize_b)\)的。
模板題傳送門:https://www.luogu.org/problemnew/show/P3377
時間複雜度:\(O(mlogn)\)
空間複雜度:\(O(n)\)
程式碼如下:
#include <cstdio> #include <algorithm> using namespace std; const int maxn=1e5+5; int n,m; int son[maxn][2]; int v[maxn],fa[maxn],dist[maxn]; int read() { int x=0,f=1;char ch=getchar(); for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1; for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0'; return x*f; } int find(int x) { while(fa[x])x=fa[x]; return x;//由於樹型結構會改變所以不敢路徑壓縮 } int merge(int a,int b) { if(!a||!b)return a+b; if(v[a]>v[b])swap(a,b); son[a][1]=merge(son[a][1],b); fa[son[a][1]]=a; if(dist[son[a][1]]>dist[son[a][0]]) swap(son[a][1],son[a][0]); dist[a]=dist[son[a][1]]+1; return a; } void pop(int u) { printf("%d\n",v[u]);v[u]=-1; fa[son[u][0]]=fa[son[u][1]]=0; merge(son[u][0],son[u][1]); son[u][0]=son[u][1]=0; } int main() { n=read(),m=read(); for(int i=1;i<=n;i++) v[i]=read(); for(int i=1;i<=m;i++) { int opt=read(); if(opt==1) { int x=read(),y=read(); if(v[x]==-1||v[y]==-1)continue; x=find(x),y=find(y); if(x==y)continue; merge(x,y); } else { int u=read(); if(v[u]==-1) {puts("-1");continue;} u=find(u),pop(u); } } return 0; }