在初學OI時,我們接觸了一種數據結構,叫做堆。
眾所周知的,我們可以使用 \(STL\) 的 \(priority\_queue\) 來快速地實現一個堆。
\[\tiny\text{如圖,這就是一個普通的小根堆} \]
利用 \(priority\_queue\) ,我們可以很方便地進行堆的添加,刪除等操作。
然而,當題目需要你進行堆的合並時, \(priority\_queue\) 便不再那么適用了。因此我們需要學習一些新的算法——左偏樹
左偏樹是一種樹形結構,儲存了一個點的數值,左右兒子和距離(被定義為它子樹中離他最近的外節點到這個節點的距離)
\[\tiny\text{如圖,這是一個普通的左偏樹} \]
左偏樹有以下幾個性質:
-
節點的權值小於等於它左右兒子的權值
-
節點的左兒子的距離不小於右兒子的距離
-
節點的距離等於右兒子的距離+1
-
一個n個節點的左偏樹距離最大為 \(\log{(n+1)}-1\)
可以看出,左偏樹本身並不是平衡的,而是所有節點倒向左側,即名字中的左偏(需要說明的是,左偏指的不是左右兒子的大小,因此如果左兒子是一個點,而右兒子是一條很長的鏈,那也是滿足要求的左偏樹)
概念到此為止,我們來考慮一下如何把兩個堆合並
首先,我們只需要將堆2加入堆1倒數第二層的點的右兒子的左兒子,因此我們只看右兒子
然后將堆2變為堆1的左兒子
合並
還原到初始的狀態
最后,由於所得堆不符合左偏樹性質,交換左右兒子
至此,我們成功地將兩個堆合並在了一起
用語言口胡一下以上過程,是這樣的:
-
找到所需要的右兒子
-
將新堆變為右兒子的左兒子
-
找回原來的堆
-
如果不左偏,交換左右兒子
既然原理都明白,代碼就很好實現了吧
#define lson tree[x].son[0]
#define rson tree[x].son[1]
struct LeftTree
{
int dis,v,son[2],root;
}tree[maxn];
inline void swap(int &x,int &y){x^=y^=x^=y;}
inline int merge(int x,int y)
{
if(!x||!y) return x+y;
if( tree[x].v>tree[y].v || (tree[x].v==tree[y].v && x>y) ) swap(x,y);//驗證是否需要交換
rson=merge(rson,y);//遞歸右兒子
if(tree[lson].dis<tree[rson].dis) swap(lson,rson);//左邊更淺就交換
tree[lson].root=tree[rson].root=tree[x].root=x;
tree[x].dis=tree[rson].dis+1;//樹的深度為右兒子深度+1
return x;
}
inline void pop(int x)
{
tree[x].v=-1;//數值清空
tree[lson].root=lson;
tree[rson].root=rson;
tree[x].root=merge(lson,rson);
}