左偏樹
Noip大概率翻皮水了,然后先繼續xjb學習吧,順便文化課也是翻皮水大隊的:(
今天介紹一種特殊的數據結構:可並堆中的一種->左偏樹(好吧其實是因為這種簡單易懂代碼復雜度較低).
基本介紹
左偏樹,故名思義,它是顆向左傾斜的樹,其實,它還是棵二叉樹,再者,它還具有堆的性質,but,它不是堆.
那么顯然,左偏樹看起來就像是優化堆一些難以用較優復雜度實現的操作,其實主要就是一個操作:合並.
我們知道,傳統的二叉堆,是需要暴力合並的,復雜度為\(O(sz1+sz2)\),而本文所涉及的左偏樹,復雜度為$O(log_{2} sz1sz2) $.
首先介紹一個定義:一個節點到其子樹內最近葉節點的距離稱之為這個節點的高度Height,簡記為ht.
接下來給出一些左偏樹的性質.
基本性質
性質1
堆的性質:對於任意節點P,\(val_{P}\)<(或>)\(val_{lson[p]}\)與\(val_{rson[p]}\)
性質2
左偏樹顧名思義,向左傾斜的樹,就是這個性質在圖像可視化后的詮釋.
性質3
很好理解,由性質2以及葉節點ht為0可以得出.
性質4
對於一棵\(n\)節點的左偏樹,\(max\{ht\} \leq log_{2}(n+1)-1\)
給出證明如下,設\(max\{ht\}=k\)
那么,顯然節點數最少的情況為一棵滿二叉樹,此時,節點數為\(2^{k+1}-1\)
故\(n \geq 2^{k+1}-1\),由數學推導可得性質4.
這個性質主要保證了左偏樹的復雜度.
接下來簡單介紹一下一些基本操作.
基本操作
1.合並
這是左偏樹最基礎也是最重要的操作,這里介紹小根堆的情況:
首先,令根節點權值較小的為x,否則為y,記根節點分別為X、Y.
首先在根節點的右子樹最右鏈中找到第一個比Y大的位置,將Y作為其父親,然后不斷遞歸調用合並Y的右子樹和以該節點為根的右子樹即可,並維護更新相關信息.
然后發現更新后,右子樹的ht可能比左子樹大,此時交換兩個子樹即可.
其實上述合並過程的實現本質是一樣的,下面給出參考,對於復雜度的分析,不多給出解釋.
inline int merge(int x,int y){
if (!x||!y) return x+y;
if (v[x]>v[y]||(v[x]==v[y]&&x>y)) swap(x,y);
ch[x][1]=merge(ch[x][1],y);
f[ch[x][1]]=x;
if (ht[ch[x][0]]<ht[ch[x][1]]) swap(ch[x][0],ch[x][1]);
ht[x]=ht[ch[x][1]]+1;return x;
}
2.插入
本質上就是將一棵只有一個節點的左偏樹與當前樹合並.
3.查詢最大/最小值
直接返回根結點的權值.
4.刪除根節點
合並根節點兩棵子樹即可.
5.構造一棵新左偏樹
法1:
暴力插入
法2:
分治思想,分段處理,直至剩余1個節點,然后返回並merge,復雜度約為O(n).
6.刪除任意節點
子樹內維護與刪除根節點一樣,然后向上傳遞信息並更改,維護性質即可.
模板
給出模板題的模板
見下:
#include <stdio.h>
#define MN 100005
#define R register
inline int read(){
R int x; R bool f; R char c;
for (f=0; (c=getchar())<'0'||c>'9'; f=c=='-');
for (x=c-'0'; (c=getchar())>='0'&&c<='9'; x=(x<<3)+(x<<1)+c-'0');
return f?-x:x;
}
inline int max(int a,int b){return a>b?a:b;}
inline int min(int a,int b){return a<b?a:b;}
inline void swap(int &a,int &b){a^=b,b^=a,a^=b;}
int n,q,ch[MN][2],v[MN],ht[MN],f[MN];
inline int gf(int u){
while(f[u]) u=f[u];
return u;
}
inline int merge(int x,int y){
if (!x||!y) return x+y;
if (v[x]>v[y]||(v[x]==v[y]&&x>y)) swap(x,y);
ch[x][1]=merge(ch[x][1],y);
f[ch[x][1]]=x;
if (ht[ch[x][0]]<ht[ch[x][1]]) swap(ch[x][0],ch[x][1]);
ht[x]=ht[ch[x][1]]+1;return x;
}
inline void pop(int x){
v[x]=-1;f[ch[x][0]]=f[ch[x][1]]=0;
merge(ch[x][0],ch[x][1]);
}
int main(){
n=read(),q=read();ht[0]=-1;
for (R int i=1; i<=n; ++i) v[i]=read();
for (R int i=1; i<=q; ++i)
if (read()==1){
R int x=read(),y=read();
if (!(~v[x])||!(~v[y])) continue;
x=gf(x),y=gf(y);if (x==y) continue;
merge(x,y);
}
else{
R int x=read();
if (!(~v[x])) puts("-1");
else {x=gf(x);printf("%d\n",v[x]);pop(x);}
}
return 0;
}
