1. 程式人生 > >左偏樹總結

左偏樹總結

mil .com ted .net 復雜 div part 決定 monkey

既然新學了左偏樹,那我就來寫一些學了左偏樹之後的總結吧。

Part 1 左偏樹是幹嘛的

首先,它支持的是兩個堆的合並過程。那麽最容易想到的是把一個堆的元素全部彈出,一個一個加入另一個堆中,就合並了。顯然這樣合並的復雜度是O(n)的,再加上程序的其他部分,很慢。我們就考慮讓復雜度減小到O(logn),很恐怖,沒錯,這就是左偏樹存在的意義。

那為什麽要叫左偏樹,顧名思義,往左偏的樹,我們首先命名一個dis[],ls[],rs[],表示一個節點到他下方最近的空節點的距離,左孩子,右孩子。顯然,為了維護左偏那麽dis[ls[i]]一定要維護大於dis[ys[i]],如果小於了,就換。並且為了保證這個點的dis是離空節點最近的,顯然是維護dis[i]=dis[rs[i]]+1。至於為什麽一定要左偏,是因為我們之後的操作是往右合並,所以操作次數會減少。(具體看後文把)

Part 2 實現過程(以大根堆為例,小根堆類推)

·合並過程

我們來模擬一下,如果兩個堆A,B要合並,那麽新的堆頂元素一定是兩個堆堆頂元素的最大值。這是必然的,那麽我們在比較完之後,把兩個堆頂的最大值放在A堆的堆頂,再把小的那一個放在B堆的堆頂,此時A堆的堆頂已經對後面的合並沒有影響了,就可以繼續合並更小的值了,我們考慮把B堆往A堆的右孩子決定( 需要註意:我們在整個左偏樹合並過程中,都直接以堆頂的編號作為堆的序號,所以到了右孩子,就相當於到了以右孩子為堆頂的一個堆),這樣不就是一個遞歸嘛,不停地比較,然後合並新堆和右孩子。

    int Merge(rg int A,rg int
B)//合並A堆和B堆 { if(!A||!B)return A+B;//如果有一個堆空了(堆頂元素編號為0),就返回另一個堆頂 if(key[A]<key[B])swap(A,B);//維護大根堆 rs[A]=Merge(rs[A],B);//新的A的右孩子是A的右孩子和新B合並的結果 if(dis[ls[A]]<dis[rs[A]])swap(ls[A],rs[A]);//維護左偏 dis[A]=dis[rs[A]]+1;//維護當前節點的dis值 return A;//返回堆頂元素
}

·刪除過程

很容易想到,刪除堆頂元素,不就是把堆頂元素的左孩子和右孩子合並嘛

    int Delete(rg int A)//刪除A堆的堆頂元素  
    {  
        return Merge(ls[A],rs[A]);//合並A的左孩子和右孩子,並返回堆頂  
    }  

Part 3 幾道左偏樹的題

T1 洛谷P1552 [APIO2012]派遣

我寫的題解emmm:[洛谷P1552] [APIO2012]派遣

T2 洛谷P3377 【模板】左偏樹(可並堆)

T3 洛谷P1456 Monkey King

//這兩道實在不行可以去看看洛谷的題解

T4 洛谷P3261 [JLOI2015]城池攻占

我寫的題解emmm:[洛谷P3261] [JLOI2015]城池攻占

最後我再推薦一位大佬的博客,我就是和他學的左偏樹租酥雨的左偏樹

左偏樹總結