簡介orm
二叉排序樹(Binary Sort Tree)又稱二叉查找樹(Binary Search Tree),亦稱二叉搜索樹。blog
二叉排序樹或者是一棵空樹,或者是具備下列性質的二叉樹:排序
若左子樹不空,則左子樹上全部結點的值均小於或等於它的根結點的值;若右子樹不空,則右子樹上全部結點的值均大於或等於它的根結點的值;左、右子樹也分別爲二叉排序樹教程
一樣的序列,由於排序不一樣,可能會生成不一樣的二叉排序樹,查找效率性對就不必定了。若是是二叉排序樹退化成一條鏈,效率就很低。隊列
伸展樹(Splay)是一種平衡二叉樹,即優化後的二叉查找樹。伸展樹能夠自我調整,這就要依靠伸展操做Splay(x,S),使得提高效率。圖片
多圖預警
圖片均爲原創,協議爲CC0,儘可能保留原地址。
劼司機的圖片風格太贊啦
前置技能
線段樹。
變量定義
N:常量,節點個數。
ch[N][2]:二維數組,ch[x][0]表明x的左兒子,ch[x][1]表明x的右兒子。
val[N]:一維數組,val[x]表明x存儲的值。
cnt[N]:一維數組,cnt[x]表明x存儲的重複權值的個數。
par[N]:一維數組,par[x]表明x的父節點。
size[N]:一維數組,size[x]表明x子樹下的儲存的權值數(包括重複權值)。
各類操做
chk操做
輔助操做,查詢一個節點位於其父節點的方向。
pushup操做
輔助操做,更新size數組的值。
旋轉(rotate)
Splay使用旋轉保持平衡。因此旋轉是最重要的操做,也是最核心的操做。
Splay旋轉後,中序遍歷和Splay的合法性不變。
好比最開始的樹是這樣子的:
如今咱們想把2號點搞到號點的位置。
那麼2下面的子樹就有1,3,4,5。一種比較優秀的玩法是這樣的:
那麼咱們能夠考慮這麼操做:
先把4→2的邊改爲4→3。再把6→4的邊改爲6→2。最後把2→3的邊改爲2→4。
第一次連邊
第二次連邊
第三次連邊
連邊前(原圖)
旋轉操做有四種。自行模擬後發現:
旋轉後,父節點會將連向需旋轉的該子節點的方向的邊連向該子節點位於其父節點方向的反方向的節點。
令x = 該節點, y = par[x], k = chk(x), w = ch[x][k^1],則ch[y][k] = w; par[w] = y;
旋轉後,爺爺節點會將連向父節點的邊連向需旋轉的該節點。
ch[z][chk(y)] = x; par[x] = z;
旋轉後,需旋轉的該節點會將連向該子節點位於其父節點方向的反方向的子節點的邊連向其父節點。
ch[x][k^1] = y; par[y] = x;
綜合一下,獲得下列代碼(可見天然語言是多麼的無力):
伸展(splay)
將一個節點一路rotate到指定節點的兒子。
注意,若是該節點、該父節點和該爺爺節點「三點一線」,那麼應該先旋轉父節點。
此處進行的操做是將3 splay到根節點。
原圖
旋轉父節點後
旋轉自身後
剩下的狀況自行模擬(沒圖片)了。
而且注意處理爺爺節點已是目標的狀況。
find操做
輔助操做,將最大的小於等於的數所在的節點splay到根。
插入(insert)
從根節點開始,一路搜索下去。若是節點存在則直接自增cnt的值。不然新建節點並與父節點連邊。
由於新建節點時可能會拉出一條鏈,因此新建節點後須要將該節點splay到根節點。沿途的rotate操做可使平衡樹恢復平衡。
查詢k大(kth)
從根節點開始,一路搜索下去。每次判斷要走向哪一個子樹。注意考慮重複權值。
查詢rank(rank)
並不須要專門寫操做。將該節點find到根後返回左子樹的權值數便可。
前驅(pre)
將該節點find到根後返回左子樹最右邊的節點便可。
後繼(succ)
同理,返回右子樹最左邊的節點便可。
刪除(remove)
顯然,任何一個數的前驅和後繼之間只有它自身。
令該點的前驅爲,後繼爲。
那麼能夠考慮把前驅splay到根,後繼splay到前驅的右兒子,那麼後繼的左兒子就是要刪除的點。
最後判特判權值數大於的狀況便可。
區間反轉
考慮線段樹維護區間標記的方法,將其移植到Splay便可。
打標記時,將和分別旋轉到根節點和根節點右兒子處,那麼的左子樹便是區間。在其根處打上標記而後在查詢大和輸出中序遍歷時下傳標記便可。
// 這張圖有點小
區間打標記
平衡樹像線段樹同樣,能夠打標記。可是有一個不一樣點,就是平衡樹的每一個節點都有權值。因此更新標記時和線段樹不同,要考慮自身節點的權值。
由於Splay能夠直接提取指定區間,因此Splay的區間操做在某些意義上比線段樹還好寫。
例題 P2042 維護數列(https://www.luogu.org/problemnew/show/P2042)
策爺:「splay/塊狀鏈表的自虐題。」
看到插入、刪除、反轉就很容易想到fhq-treapSplay。
簡化版問題
若是隻考慮修改、求和、求最大子段和,就能夠直接用線段樹解決。
考慮維護la[N]、ra[N]、gss[N]、sum[N]、upd[N],分別表明最大前綴和、最大後綴和、最大子段和、區間和和修改標記。
初始化la、ra時,在選與不選之間取max便可。gss則初始化爲葉子的值便可。
la[x] = ra[x] = max(0, sum[x]); gss[x] = sum[x];
考慮如何維護la、ra、gss和sum。
再考慮如何維護upd。
upd的存儲方式其實有兩種:一種是把須要更新的值存儲起來,另外一種是修改時直接更新完畢,而後再打上bool標記。這裏我採用的是後者。
下傳也簡單。將整個區間set成同一個值後,la、ra和gss的更新與初始化有些類似。
la和ra的代碼不變,gss改爲在選所有與選一個之間取max(題目要求必須選一個)。
沒了?固然還有。
完整版問題
如今多了插入、刪除和區間反轉,維護方法類似。這裏咱們先考慮每一個點都有權值後的變化。
其中用括號括起來的是增長的部分。
考慮同時下傳反轉和set兩個標記。若是區間所有設置爲一個值,反轉也就沒有意義了。因此處理順序是set→反轉。
pushdown的完整代碼以下:
垃圾回收
這個毒瘤題很是噁心,卡我空間,只好寫個辣雞垃圾回收。
刪除的時候,把要刪除的節點所有加到一個隊列裏。等到要插入的時候,優先使用隊列裏的點。
代碼很好理解。
其餘用途
Splay由於其超強的區間操做能力,因此也做爲LCT的輔助樹使用。
Splay也能夠搭配仙人掌剖分樹鏈剖分,把一些序列上的題目出到仙人掌樹上。
代碼
下面附上我那常數巨大的代碼,供參考用:
普通平衡樹
文藝平衡樹
P2042
本文發佈於洛穀日報,特約做者:tiger0132
原文地址:https://tiger0132.blog.luogu.org/slay-notes