1. 程式人生 > 其它 >筆記-左偏樹

筆記-左偏樹

前置芝士:堆

可並堆

可並堆就是滿足堆性質且可以在\(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年的論文,實際上是我太弱了不會做