左偏樹是可並堆的一種。除了支援堆的所有操作之外,左偏樹還支援合併兩個堆並把複雜度維持在 \(o(n \log n)\) 級別。
dist
外節點:對於一棵二叉樹,我們定義外節點為左兒子或右兒子為空的節點。
dist:我們定義某乙個節點的 \(\text\) 為該點到最近的外節點的距離。外節點的 \(\text\) 為 \(1\)。
我們不難發現,乙個有 \(n\) 個節點的堆,根的 \(\text\) 不超過 \(\left\lceil\log(n+1)\right\rceil\)。假設乙個根的 \(\text\) 為 \(d\),顯然它至少有 \(2^d-1\) 個節點。取對數即得到上述結論。
左偏樹為何左偏
之所以叫「左偏樹」,因為在左偏樹中,對於任何乙個節點,左兒子的 \(\text\) 一定大於右兒子的 \(\text\)。
正因為如此,左偏樹中任何乙個節點的 \(\text\) 為右兒子的 \(\text\) 加 \(1\)。
左偏樹的操作
合併兩個堆是乙個遞迴的過程。我們令當前要合併的的兩個堆堆頂分別為 \(x,y\) 且 \(val_x。合併過程如下:
對於這兩個堆,合併後的堆頂為 \(x\)。
令新堆頂 \(x\) 的左子樹為 \(x\) 的左子樹(其實就是左子樹不變)。
遞迴合併 \(x\) 的右子樹這個「子堆」和 \(y\),作為新堆頂 \(x\) 的右子樹。
回溯時檢查 \(x\) 左兒子和右兒子的 \(\text\)。若左兒子的 \(\text\) 小於右兒子的 \(\text\) 則交換兩個兒子。
int merge(int x, int y)
有了 merge 的操作,我們 pop 就不必那麼麻煩了。如果我們想彈出乙個點 \(x\),只需要合併 \(x\) 的左右子樹然後把 \(x\) 刪掉。
同理,我們插入節點時也可以直接把待插入節點 \(x\) 當成乙個堆,直接執行 merge 操作即可。
我們常常需要照到某個點 \(x\) 的堆頂 \(rt\)。如果直接向上跳單次複雜度可能退化到 \(o(n)\)。為了避免這種情況的發生,我們需要用並查集維護點在哪個堆裡面。具體操作和並查集類似。
需要注意的是為了維護 father 關係,我們執行 merge 時要把合併的兩個堆頂節點 \(x,y\) 的父親都設為 merge 函式的返回值(即新的堆頂)。考慮到路徑壓縮的存在,執行 pop 操作時還要把被 pop 的節點的父親設為其兩個兒子 merge 後的新堆頂。
luogu 上的模板題,要求支援 merge 和 pop 的操作。
struct node
node[maxn]; int n, m;
int find(int x)
int merge(int x, int y)
void pop(int x)
int main()
int op, x, y;
for(int i = 1; i <= m; ++i)
if(op == 2)
printf("%d\n", node[find(x)].val); pop(find(x));
} }return 0;
}
左偏樹學習筆記
左偏樹是一種基於二叉樹的可並堆。定義乙個節點的 距離 dis xdis x disx 為它到空節點的最短路長度,左偏樹強制要求 dis lson dis rson dis ge dis dislso n d isrs on 所以 dis x di srso n 1dis x dis 1 disx d...
左偏樹 學習筆記
首先要知道左偏樹是用來幹什麼的。如果給我們兩個優先序列,然後讓我把這兩個優先佇列合併成乙個優先佇列。如果直接用堆,就是將乙個佇列裡面的數不斷彈出然後扔到另乙個佇列裡。複雜度是 o n n為佇列中數的個數。但是用左偏樹就可以做到 log n 1 n 2 ps 為了便於討論,本文所有的左偏樹均已小根樹為...
學習筆記 左偏樹
左偏樹是一種可並堆,除了堆的基本功能,最大的特點就是支援合併堆,甚至比普通堆好寫。下面敘述以小根堆為例,大根堆對稱。o log n 求乙個數所在堆的根 o 1 求最小值 o log n 合併兩個堆 o log n 刪除最小值 o log n 插入乙個數 n 是插入的總節點數 或當前堆的節點數 str...