顧名思義就是沒有旋轉操做的treap.
仍是很好打的.
畢竟旋轉操做旋轉上天.html
兩個核心操做: split和merge函數
split是將一棵樹分紅兩棵樹的操做.
注意這裏的要求是對於肯定的樹,將其前k個點分紅新樹, 剩下的點變成另外一顆新樹,所以可能出現多個切割的地方.
對於一個節點來講,咱們必然只會處理它的一顆子樹,所以用遞歸去找處理的子樹就好了.
返回時對於每個點更新一下它被處理的那顆子樹.
函數的返回值是兩顆子樹的根.
具體看代碼吧學習
define pii pair<int,int> define mp make_pair pii split(int rt, int k) { //對於根爲rt的樹,將它前k個點裂成一棵樹A,剩下的點成爲樹B if (!rt) return mp(0, 0); pii tmp; pushdown(rt); if (k > S[C[rt][0]]) { //處理右子樹 tmp = split(C[rt][1], k - S[C[rt][0]] - 1); //tmp表示右子樹分裂出來的兩棵樹(A,B), //其中左邊(A)的是當前rt的新的右子樹 C[rt][1] = tmp.first; pushup(rt); tmp.first = rt; //更新rt, 而後將rt做爲一個新的左子樹, //原右子樹分裂出來的右邊的新樹(B)做爲新的右子樹, //將這顆新樹返回 } else { //處理左子樹 tmp = split(C[rt][0], k); C[rt][0] = tmp.second; pushup(rt); tmp.second = rt; } return tmp; }
merge便是將兩顆樹合併的操做, 注意這裏合併的樹(A,B)要求max_value(A) < min_value(B),這樣把AB一左一右相接(即保證B的每個節點都在A的右邊)便保證了權值的有序,咱們就只要維護堆的性質了.(顯然split分出來的兩顆樹就知足這樣的性質)ui
int merge(int ra, int rb) { //返回新樹的根 if (!ra) return rb; if (!rb) return ra; //有一顆空樹,直接合並 pushdown(ra); pushdown(rb); if (KEY[ra] < KEY[rb]) { //ra的key值較小,維護小根堆的話要放在上面 C[ra][1] = merge(C[ra][1], rb); //默認rb是接在右邊的樹,所以rb必然會接進ra的右子樹中 pushup(ra); return ra; //不要忘記更新 } else { C[rb][0] = merge(ra, C[rb][0]); pushup(rb); return rb; } }
這裏是題目.
單點操做基本都能靠merge+split完成.
好比這題只需加上splay中同樣的getkth(找到第k個數), findkth(找到數A的位置),
那麼:
insert=getkth(findkth+getkth)+split+merge
delete=getkth(findkth+getkth)+split+merge
單點插入刪除也可用(merge)(split)完成.code
這裏是題目.
其實和單點操做沒什麼區別...
區間的插入刪除也是使用(merge)(split)完成.
刪除好說,可是注意插入時須要咱們先建好一顆子樹再merge.
因而又有了一個build函數.
咱們能夠一個一個把點插到新樹中去(一開始有一顆空樹).
那麼每次插入的點必然在樹的最右端.htm
而後開始維護小根堆的性質.
考慮root -> right son -> right son ... 這樣一條鏈, 咱們先把新點接在這條鏈最下面,
而後找到其中深度最小的一個key值大於大於點的節點,把以它爲根的子樹當作新點
的左子樹, 而後用新點代替它原來的位置就能夠了.(至關於把新點沿着鏈一直向上旋)
可是須要注意排布在這條鏈上的樹是沒有維護(pushup/update)過的, 所以每次
尋找到要被移到新點下面的點都須要一次pushup, 最後再給仍在鏈上的點來一發pushup.
由於每次加入的點都在鏈上, 能夠證實每一個點都會(在它的全部子樹以後)通過一次pushupblog
還有一個須要注意的點是splay中的虛點.
無旋treap並不須要虛點,可是在pushup的時候可能考慮到空子樹的狀況,爲避免空子樹的影響
須要一個初始化.遞歸