平衡樹 - FHQ 學習筆記
平衡樹 - FHQ 學習筆記
本片文章的姊妹篇:平衡樹 - Splay 學習筆記。
感覺完全不會平衡樹,又重新學習了一遍 FHQ,一口氣把常見套路都學完了。
一、大致內容及分類
FHQ,全稱非旋轉 Treap,是一種可以用於維護按權值、排名分裂的資料結構。它相比與 Splay 雖然常數較大,但是實現起來程式碼難度相對容易,而且由於它非旋的特點,也可以用來實現可持久化。
既然叫做非旋 Treap,它兼有 Treap 的特點又有非旋轉獨特的優勢。
- 從 Treap 角度看,他們同樣都是依賴修正值
rnd
是隨機的,用將他們按照rnd
形成一個小根堆 - 從非旋角度看,FHQ 直接通過
split
和merge
操作實現新增、刪除元素,不用再樹上旋轉了。
根據不同題目要求,將平衡樹分為序列平衡樹和權值平衡樹。
- 序列平衡樹的中序遍歷為每個元素的下標,權值為每個元素具體的值,常見題型為區間翻轉等。
- 權值平衡樹的中序遍歷是所有元素的排名,即按照中序遍歷提取所有元素後元素權值遞增,常見操作為全域性第 \(k\) 大等。
如果毒瘤出題人同時綜合了以上兩種操作,即區間翻轉 \(+\) 區間第 \(k\) 大,應該怎麼做呢?好吧,如果真是這樣,這篇文章可能不能夠幫到你,用用樹套樹吧。
二、基本操作
FHQ 的核心操作就是 split
出操作區間,操作完後 merge
回去。
下邊講解中預設的平衡樹型別為權值平衡樹,序列平衡樹其實是將某些 \(val\) 改為了 \(siz\)。
分裂 split
無論是按照排名還是權值分裂,他們都是將原樹分為左右兩半,可以利用中序遍歷的性質進行分裂。
具體操作時,我們新建兩個臨時變數 \(x,y\) 分別表示分裂出來的左邊、右邊的那顆平衡樹。
如果我們遇到一個應該屬於 \(x\) 樹的節點,就將這個點以及這個點的左子樹加入 \(x\) 樹中,並遞迴分裂右子樹;如果遇到屬於 \(y\) 的,就將這個點與它的右子樹加入 \(y\) 樹中,並遞迴分裂左子樹。
可以寫出虛擬碼如下:
void split(int p,int k,int &x,int &y) // 分裂出 (-infty,k],(k,+infty)
{
if(!p) { x=y=0; return; }
pushdown(p);
if(tree[p].val<=k) x=p,split(tree[p].pr,k,tree[x].pr,y);
else y=p,split(tree[p].pl,k,x,tree[y].pl);
pushup(p);
}
合併 merge
由於這是 FHQ 的 merge
,需要在合併時既保證小根堆性質又不破壞中序遍歷的特點,對合並的兩棵樹有特殊的要求:左右區間不能夠相交或者順序顛倒!
所以我們在合併時必須按照順序從左到右合併。
具體操作時,可以直接將 rnd
小的作為新樹的根節點,如果這個根節點來自左子樹就遞迴右子樹,相反來自右子樹就遞迴左子樹(由於滿足上面區間不相交也不顛倒的特點)。
寫出虛擬碼:
int merge(int x,int y)
{
if(!x || !y) return x+y;
if(tree[x].rnd<tree[y].rnd)
pushdown(x),tree[x].pr=merge(tree[x].pr,y),pushup(x);
else
pushdown(y),tree[y].pl=merge(x,tree[y].pl),pushup(y);
}
新建節點 new
沒什麼可說的,就是給新節點附一個隨機的 rnd
。
inline int New(int Val)
{
tree[++cnt].rnd=rand();
tree[cnt].val=Val;
tree[cnt].siz=1;
tree[cnt].pl=tree[cnt].pr=0;
return cnt;
}
插入 insert
直接分裂出兩端區間,把新建的加點放到兩棵樹中間在合併即可。
inline void Insert(int val)
{
split(root,val,x,y);
root=merge(merge(x,New(val)),y);
}
刪除 delete
FHQ 可以實現刪除一個數或刪除這個值的所有數,唯一區別就在於分裂時的不同。
inline void Delete_one(int val)
{
split(root,val,x,z);
split(x,val-1,x,y);
root=merge(x,z);
}
inline void Delete_all(int val)
{
split(root,val,x,z);
split(x,val-1,x,y);
y=merge(tree[y].pl,tree[y].pr);
root=merge(merge(x,y),z);
}