筆記-左偏樹
前置芝士:堆
可並堆
可並堆就是滿足堆性質且可以在\(O(\log{n})\)的複雜度內合併兩個堆的資料結構。常見的有斜堆,左偏樹等。
這篇文章就來介紹一下左偏樹。
首先有個問題:直接用二叉堆不香嗎?
答案是肯定的。因為二叉堆合併的時間複雜度是\(O(n)\)的。
所以我們有了左偏樹這個資料結構。
左偏樹
一些記號與定義
val:每個節點的權值,堆按這個排序。
外節點:沒有左孩子或右孩子或都沒有的節點。
dist:空節點的dist是0,外節點的dist是1,其餘節點的dist為到最近的外節點的距離。
一棵左偏樹的dist:記堆頂的dist為這課左偏樹的dist。
性質
左偏樹顧名思義就是朝左偏的樹。它在滿足堆性質的基礎上還要滿足左孩子的dist大於等於右孩子的dist(所以左偏嘛)。
在合併時全部懟到右孩子去就可以保證在log的時間複雜度內合併了。
顯然,對於一個非外節點的dist等於其右孩子的dist+1。
基本操作
合併
合併時左偏樹最重要的一步,我們以小根堆為例。
假設我們要合併兩個堆的堆頂分別是u,v。
於是我們為了滿足小根堆的性質找出val較小的點做為新左偏樹的頂點,這裡不妨設其為u。
於是基於啟發式合併的思想,我們把u的右孩子和v合併,遞迴去做就可以了。
因為一棵左偏樹的dist最多是\(ceil(\log{n})\),每次合併必有一棵左偏樹的dist減1,所以這樣合併的複雜度是\(O(\log{n}+\log{m})\)。
合併完有可能打破左偏樹的性質,要更新。
int Merge(int u, int v) {
if (!u || !v) return u | v;
if (val[u] > val[v]) swap(u, v);
rson[u] = merge(rson[u], v);
if (dist[lson[u]] < dist[rson[u]]) swap(lson[u], rson[u]);
dist[u] = dist[rson[u]] + 1;
return u;
}
查詢最小(最大值)
直接返回堆頂即可。
刪除最小(最大值)
合併堆頂的兩個孩子即可
刪除任意給定節點
注意這裡指的是給定節點,就是說告訴你了是幾號節點。左偏樹是不能快速刪除某個權值的所有節點的。
這東西沒什麼鳥用,反正我至今沒有遇到這樣的題,就不講了。其實是我太弱了。
思想和刪除堆頂類似。
建樹
像並查集一樣,每個點就是一棵左偏樹,然後再合併即可。
插入新節點同理直接合並即可。
給整個堆加上或乘上某個數
類似於線段樹懶標記的思想,給堆頂打個標記,合併的時候再通過pushdown下放下去。
後面有一道例題。
例題
羅馬遊戲(模板題)
Monkey King(維護大根堆)
[APIO2012]派遣(樹上問題)
[JLOI2015]城池攻佔(樹上問題+打標記)
[SCOI2011]棘手的操作(學業繁忙,筆者還沒實現)
[BalticOI 2004]Sequence 數字序列(這是一道論文題,詳情請見2004年的論文,實際上是我太弱了不會做)