可並堆講解
注:本篇博客以大根堆為例。
可並堆,顧名思義,就是可以合並的堆。堆滿足一個性質,就是當前節點,都大於或者等於他的所有子樹上的節點,自然在這里我所講的是結點的權值。顯而易見,既然可並堆是堆的一種,容易推出,可並堆也滿足這個性質。
現在思考一個問題,當題目里需要合並兩個堆的時候,該如何合並呢?如果只是普通的堆的話,我們可以運用啟發式合並的思想,每一次把size小的堆的點暴力插入到size大的堆里面,這個方法十分不優秀,時間復雜度是O(nlog2n)。所以我們會用可並堆來實現這個問題。
可並堆是一種運用到左偏樹思想的堆,何為左偏樹?左偏樹顧名思義就是偏向左面的樹,如左圖就是一棵左偏樹,這棵樹明顯滿足一個性質。我們定義dis[p]為從p節點出發可以向右走的最大步數,從右圖中可以看到,當前點的左兒子的dis值都比右兒子的dis值大,只要滿足這個性質就是左偏樹。
學過左偏樹之后,我們就可以學習可並堆了,可並堆的難點就是合並,那么應該如何合並呢?為了能保證時間復雜度,我們每一次合並都應該把新的節點放在當前的節點的右兒子上,這個是一個遞歸的過程,每一次把當前的兩個節點進行比較,留下權值大的點,然后遞歸下去把另一個點和留下的點的右兒子進行比較,如此下去,進行合並。當然每一次回溯之前,我們都需要判斷當前節點的左右兩個兒子的dis值,如果右兒子的dis值大於左兒子的dis值,則交換左右兒子。
int merge(int x,int y) { if(!x) return y; if(!y) return x; if(num[x]<num[y]) swap(x,y); son[x][1]=merge(son[x][1],y); if(dis[son[x][1]]>dis[son[x][0]]) swap(son[x][1],son[x][0]); dis[x]=dis[son[x][1]]+1; return x; } //son[p][0]表示p號節點的左兒子 //son[p][1]表示p號節點的右兒子 //num[p]表示p號節點的權值
下面就是刪除節點(最大值),只需要把他的兩個兒子合並就好啦,是不是很簡單?代碼就不附了,具體題目具體分析。
大致就是這樣,不會的可以評論發問題,我會解答。
題目(我會不斷更新)
bzoj1455&&luogu2713羅馬游戲:http://www.cnblogs.com/yangsongyi/p/8893005.html
APIO2012派遣:http://www.cnblogs.com/yangsongyi/p/8921448.html
JLOI2015城池攻占:http://www.cnblogs.com/yangsongyi/p/9046913.html