日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【原创】/Restarting/ Splay树 (普通平衡树 文艺平衡树 bzoj1895 poj 2580 SuperMemo 题解)

發(fā)布時間:2023/12/16 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【原创】/Restarting/ Splay树 (普通平衡树 文艺平衡树 bzoj1895 poj 2580 SuperMemo 题解) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Index

  • Splay
    • 說在前面
    • splay樹的基本思路
    • 基本的定義
    • splay函數(shù)
      • 旋轉(zhuǎn) rotate
      • 伸展 splay
    • 插入 insert
    • 前驅(qū)&后繼 pre&nxt
    • 求數(shù)的排名和排名上的數(shù)
    • 刪除 deleted
    • 合并 join
    • 分離 split
    • 求最值 min&max
    • 翻轉(zhuǎn) turn
    • 其他區(qū)間操作(以SuperMemo為例)
      • 翻譯
      • 一個個來
    • 代碼
      • 普通平衡樹
      • 文藝平衡樹
      • SuperMemo
    • Splay的優(yōu)缺點
    • 參考文章

Splay

說在前面

關(guān)于二叉平衡樹是什么以及AVL樹的實現(xiàn)
用vector實現(xiàn)普通平衡樹!
qwq

標(biāo)題好長!
真是聲勢浩大,徒有其表。

splay樹的基本思路

出于某些原因(cache原理),在訪問了某個節(jié)點之后,接下來有90%的概率很頻繁地再次訪問該節(jié)點,如果能把這個大概率會被多次訪問的結(jié)點放到離樹根盡可能近的地方,那么就可以節(jié)省不少的時間。
(大概如此)

所以要想辦法把最近訪問的結(jié)點扔到距離根節(jié)點盡可能近的位置。

著名計算機學(xué)家tarjan就想到了辦法。

基本的定義

不寫這個后文進行不下去啊。

const int MAXN=102030; struct Splay_Tree {int val,c[2],up;//c[0]代表左兒子,c[1]代表右兒子,up代表父親 }tree[MAXN];bool which(int pos) {return tree[tree[pos].up].c[1]==pos; }//返回pos是它父親的哪個兒子

splay函數(shù)

splay的意思是伸展。
接下來給出的splay函數(shù),能夠在保證一直保持著BST的結(jié)構(gòu)的同時,把某個節(jié)點伸展到根去。

怎么做呢?
參考AVL樹,我們可以一點一點地把這個點旋轉(zhuǎn)上去。

旋轉(zhuǎn) rotate


就以上圖為例子,假設(shè)被旋轉(zhuǎn)點在目標(biāo)點的左下方。
現(xiàn)在,我們要把紅色點轉(zhuǎn)到它的父親橙色點的上面。

嗯哼哼(試圖吸引注意力),我是紅點。
rotate的基本思路就是,讓我右上方的父親(因為在右邊所以比我大)成為我的右兒子

我父親之前在我的哪上方,那就讓他去我的哪下方。

然而這樣就會有另外三對父子關(guān)系收到了威脅。

也就是說……

我原來的右兒子(粉色)將何去何從?
我原來的爺爺(藍色)的兒子(橙色)怎么就沒了?
我原來的兄弟(紫色)的父親(就是我的父親橙色)怎么就沒了?

不要慌張,我們冷靜分析。
現(xiàn)在,
需要爸爸的:粉色 紫色 紅色
需要兒子的:橙色(一左一右) 藍色
正好都是三個,看來可以平均分。

這樣就沒問題了。

橙色點的右兒子還是它的右兒子(紫色)。
紅色點的右兒子(粉色)就接在新的右兒子(橙色)下面,當(dāng)左兒子。
然后再讓紅色點接到原來的爺爺(藍色)下面。

(我寫了些什么?)

如果紅點在橙色點的右下角那就照照鏡子反過來。

也就是說,
如果我的父親在我的右上方,也就是我是我父親的左兒子。
那么就把我的父親拉下來成為我的新的右兒子。
此時,我的父親的左兒子就不是我了,我的右兒子的位置被擠占了(沒了和父親(我)的連接),我爺爺?shù)膬鹤右矝]有了。
于是讓我原來的右兒子成為我的原來的父親(現(xiàn)在新的右兒子)的左兒子,然后我篡權(quán)奪位,成為我原來爺爺?shù)男聝鹤印?/p>

因此rotate函數(shù)可以這么寫:

void rotate(int pos) {int up=tree[pos].up,upup=tree[up].up,is=which(pos);//如果我是我父親的左兒子(is=0)的話,就讓我的右兒子當(dāng)我父親的新的左兒子,我父親成為我的右兒子tree[up].c[is]=tree[pos].c[!is],tree[tree[pos].c[!is]].up=up;tree[pos].c[!is]=up,tree[up].up=pos;//爸爸認兒子的同時要記得兒子認爸爸啊//我的新爸爸就是我原來的爺爺,我原來的爺爺?shù)男碌膬鹤泳褪俏襱ree[pos].up=upup;if(upup) tree[upup].c[tree[upup].c[1]==up]=pos;//當(dāng)然如果爺爺是虛空(原來的爸爸就是根節(jié)點)的話,就不能爸爸認兒子了//還有還有,因為父子關(guān)系發(fā)生了說不清道不明的改變,所以這里不好用which,要用which一開始定義的時候的用 }

伸展 splay

我們發(fā)現(xiàn)通過rotate我們能在不改變樹的平衡性的同時讓某個點上升一層,但是這離我們的目標(biāo)(旋轉(zhuǎn)到根節(jié)點)還差得遠。

所以就有了splay操作:讓某個結(jié)點通過一次又一次rotate轉(zhuǎn)到根節(jié)點。比方說:

逆流而上的你眼前或許有無數(shù)曲折崎嶇道路,也許離終點遙遙無期,但是,
結(jié)點到達根源葉子結(jié)點\small 結(jié)點~~~~~~~~~~~到達根源~~~~~~~~~~~~~~~~~~~~~葉子結(jié)點結(jié)????????????????????????????????結(jié)
人一定要有夢想,沒有夢想和咸魚有什么區(qū)別?!~人一定要有夢想,沒有夢想和咸魚有什么區(qū)別?!?區(qū)

(這就是你四暗刻一向聽的時候碰碰杠杠做對對還一張dora沒有翻出來的原因?)

所以誰能告訴我這個“逆流而上的你”是什么啊還消不掉!(見下圖)

咳咳,話說回來,逆流而上的你眼前或許有無數(shù)曲折崎嶇道路,也許離終點遙遙無期,但是,絕無無法行走的路 (定義如此,走不了就不叫路了) ,只要你想要到達,就沒有無法克服的障礙,只要你想辦法的話。
在你一步一步往上rotate的時候,你的道路大概可以分為以下三類,六種:

還有一類沒有畫上去,就是爸爸就是根節(jié)點,沒有爺爺的情況,這個直接一個rotate就解決了。

折線形沒有什么好說的,一步一步rotate上去吧。
關(guān)于直線型:就是我是爸爸的a兒子,我爸爸是我爺爺?shù)腶兒子的情況。
科學(xué)家們告訴我們,這個時候應(yīng)該先rotate(爸爸),再rotate(我)。
如果仍然是一直埋頭苦干rotate(我),這樣的自平衡方法叫做Spaly;而先rotate(父親),再rotate(我)的自平衡方法叫做splay。
如下圖:

乍一看差不多,甚至某道題目(BZOJ1036樹的統(tǒng)計Count)我把splay換成了spaly會快一些(5600ms->5000ms),但是咨詢了Freopen/Kyle/wk大佬后,Freopen/Kyle/wk告訴我,“可以證明splay更優(yōu),而且出數(shù)據(jù)的時候可以卡spaly。”
(所以說科學(xué)家等于wk?)

哇。

總結(jié)一下:

情況應(yīng)對方法
我爸爸是根,我沒有爺爺rotate(我)
我,我爸爸,我爺爺呈一條直線rotate(父),rotate(我)
我,我爸爸,我爺爺呈一條折線rotate(我),rotate(我)

所以就可以得到splay函數(shù):

void splay(int pos) {while(tree[pos].up){if(tree[tree[pos].up].up && which(tree[pos].up)==which(pos)) rotate(tree[pos].up);rotate(pos);}root=pos;//一個全局變量root來記錄splay樹的根 }

當(dāng)然,用splay操作可以使一個節(jié)點上升到它上面的任意一個結(jié)點

插入 insert

和正常的二叉平衡樹一樣,先找到對應(yīng)的位置,直接插入,沒問題的。
然后再spaly到根節(jié)點上去

void insert(int pos,int val,int up) //調(diào)用的時候就用insert(root,某個值,0) {if(!pos){tree[++n].val=val,tree[n].up=up;tree[up].c[tree[n].val>tree[up].val]=n;//認別人做爸爸的同時,別人也要認你做兒子splay(pos);//此時不同的題有不同的操作return ;}insert(tree[pos].c[val>tree[pos].val],val,pos);//這里默認每個點的值都不同,如果相同的話就在不同的題里面有不同的處理方式 }

前驅(qū)&后繼 pre&nxt

也有叫upper和lower的,還有等等名字。
和一般的二叉平衡樹沒有什么區(qū)別。

//默認了各個數(shù)不相同,不過知道了原理之后想怎么樣都無所謂吧 int pre(int val) {int pos=root,ans=-2147483647;while(pos){if(tree[pos].val<val) ans=max(ans,tree[pos].val);pos=tree[pos].c[val>tree[pos].val];}return ans; } //所謂前驅(qū)就是小于某個值的最大值,也可以說是這個點的左子樹里面最靠右的那個端點。int nxt(int val) {int pos=root,ans=2147483647;while(pos){if(tree[pos].val>val) ans=min(ans,tree[pos].val);pos=tree[pos].c[val>tree[pos].val];}return ans; } //基本上是復(fù)制之前寫AVL的時候?qū)懙哪莻€

求數(shù)的排名和排名上的數(shù)

我稱之為:getrank()getrank()getrank()rankget()rankget()rankget()

如果采用惰性方法的話就很方便。
現(xiàn)在我們給每個節(jié)點新增兩個變量,cnt和siz。
cnt代表這個點上重復(fù)有多少個數(shù)。比方說val=1的點的cnt=4,就代表插入了4個1,都被塞到同一個點里。
siz代表子樹里數(shù)的個數(shù)(也就是說,不是子樹點的個數(shù),而是子樹里各個點的cnt的值的和)
這樣子的話,插入和刪除會有些許變化。記得在樹的結(jié)構(gòu)或者點的cnt改變的時候pushup一下來維護siz。(也就是insert和delete和splay的時候→也就是splay的時候)到時候給出普通平衡樹模板的時候一并看吧。

然后rankget,就是和找前驅(qū)差不多了,用找前驅(qū)的方法加上siz這個變量就可以輕松把前驅(qū)是誰轉(zhuǎn)換成求前驅(qū)有幾個的問題了。就是找到前驅(qū)然后把前驅(qū)的siz+1就是答案了。

然后是getrank,感覺像find和getrank的結(jié)合體,有了siz和cnt,我們就知道每個pos的val所對應(yīng)的排名的區(qū)間是多少了。就是[tree[tree[pos].c[0]].siz+1,tree[tree[pos].c[0]].siz+tree[pos].cnt]\bold{\left[tree[tree[pos].c[0]].siz+1,tree[tree[pos].c[0]].siz+tree[pos].cnt\right]}[tree[tree[pos].c[0]].siz+1,tree[tree[pos].c[0]].siz+tree[pos].cnt],如果在這個范圍里,那么就找到了,如果給定的排名在這個排名區(qū)間的左邊,那就說明我們要找的數(shù)比當(dāng)前的數(shù)要小,那么就向左二分下去,如果在右邊就向右邊。
需要稍微注意一下的是,向右邊搜的時候,給定的排名要減去(tree[tree[pos].c[0]].siz+tree[pos].cnt)(tree[tree[pos].c[0]].siz+tree[pos].cnt)(tree[tree[pos].c[0]].siz+tree[pos].cnt),因為當(dāng)前節(jié)點的siz是它的左兒子+右兒子+自己,比方說左兒子和右兒子是[1,2,3,4,5,6,7]和[8,9,10]找第八名,當(dāng)然應(yīng)該在右兒子里面找了,但是右兒子只有3個數(shù)沒有第8名,所以把8-7(左兒子的siz)得到1,我們應(yīng)該在右兒子里面查詢第1大的數(shù)。

void pushup(int pos) {tree[pos].siz=tree[tree[pos].c[0]].siz+tree[tree[pos].c[1]]+tree[pos].cnt; } //目前的pushup只有siz一個,因為查找前驅(qū)后繼不會改變數(shù)的形態(tài)更不會改變siz所以不pushup//調(diào)用的時候調(diào)用getrank(val,root),對了,還有可能找不到,所以記得特判 int getrank(int val,int pos) {if(pos==0) return 2147483647;if(val==tree[pos].val) return tree[tree[pos].c[0]].siz+1;else {if(val<tree[pos].val) return getrank(val,tree[pos].c[0]);else return getrank(val,tree[pos].c[1])+tree[tree[pos].c[0]].siz+tree[pos].cnt;} }int rankget(int rak,int pos) {if(pos==0) return -2147483647;if(rak>=tree[tree[pos].c[0]].siz+1 && rak<=tree[tree[pos].c[0]].siz+tree[pos].cnt) return tree[pos].val;else{if(rak<tree[tree[pos].c[0]].siz+1) return rankget(rak,tree[pos].c[0]);return rankget(rak-tree[tree[pos].c[0]].siz-tree[pos].cnt,tree[pos].c[1]);} }

刪除 deleted

因為delete這個函數(shù)名已經(jīng)有了,所以加了一個d。

采用惰性刪除。

現(xiàn)在要分好幾種情況來討論。

首先,如果cnt-1之后還有剩余,就平安無事什么也不用干,cnt–就是了。

如果cnt==1,也就是刪除了這個點就沒有了:
(說實話空留一個cnt=0的點在那里浪費時間空間好像沒什么問題啊)
先把這個要刪除的東西splay到根節(jié)點處

如果要刪除的點(目前已經(jīng)splay到根了),沒有兒子: 這棵樹的最后的一個數(shù)被你刪了,這棵樹完了。
如果只有一個兒子:那么就直接把這個根節(jié)點移除掉,并把根節(jié)點的位置傳給那個兒子

如果有兩個兒子的話:
把前驅(qū)找到并splay上來,然后把被刪除點的右兒子接到前驅(qū)的右邊,自己消失掉

void deleted(int val) {getrank(val);//隨便整一下把目標(biāo)點splay上來if(--tree[root].cnt) return;//如果刪掉一個還有剩余,就無事發(fā)生if(!tree[root].c[0] && !tree[root].con[1]) root=0;//如果連根無子無孫,那這棵樹就沒了else if(!tree[root].c[1]) root=tree[root].c[0],tree[root].up=0,pushUp(root);else if(!tree[root].c[0]) root=tree[root].c[1],tree[root].up=0,pushUp(root);//如果只有一個兒子,那就讓那個兒子接替自己的位置else {int pre=tree[root].c[0],pos=root;while(tree[pre].c[1]) pre=tree[pre].c[1];//找前驅(qū)splay(pre),tree[tree[pos].c[1]].up=root,tree[root].c[1]=tree[pos].c[1],pushup(root);//把前驅(qū)再轉(zhuǎn)上來,現(xiàn)在目標(biāo)點(pos)就是根節(jié)點的右兒子} }

現(xiàn)在普通平衡樹的各個功能就寫好了。
然后是,

合并 join

有一顆Splay樹(記為S1)的所有節(jié)點都比另一顆Splay樹(記為S2)的最小的節(jié)點小的時候,

于是讓S1最大的節(jié)點Splay到S1的根,然后把S2接到S1的右下方。

好雞肋的功能。
圖來自楊思雨的論文。

分離 split

給定數(shù)x,把一顆splay樹分成兩棵樹,其中一棵樹的值都小于x,另一顆都大于x。
首先把x這個點splay到根,然后它的左子樹和右子樹即為所求

求最值 min&max

這個就一直往左or右走就是了。

翻轉(zhuǎn) turn

現(xiàn)在來考慮做文藝平衡樹。
文藝平衡樹要我們支持對一個數(shù)列進行區(qū)間翻轉(zhuǎn)再輸出。

首先,為了把用一棵樹來存一個數(shù)列,所以和普通的SBT不同(普通的SBT的中序遍歷是一個不下降序列)的,現(xiàn)在我們維護的splay樹的中序遍歷是這個區(qū)間本身。也就是從按權(quán)值不下降排序到下標(biāo)不下降排序

舉個例子就是一個數(shù)組{1,3,4,5,6,7,2,4,5,2},在一個普通的Splay樹中,它的中序遍歷是{1,2,2,3,4,4,5,5,6,7},在支持區(qū)間翻轉(zhuǎn)的Splay樹中,中序遍歷是**{1,3,4,5,6,7,2,4,5,2}**。

然后怎么區(qū)間翻轉(zhuǎn)呢?

先建一顆樹,按照題目所要求的,就假設(shè)N=12,那么數(shù)列就是{1,2,3,4,5,6,7,8,9,10,11,12},建成splay樹以后可以長這個樣子:

上圖的確是跑splay的時候跑出來的。

現(xiàn)在我們可以看到,中序遍歷就是{1,2,3,4,5,6,7,8,9,10,11,12},假設(shè)現(xiàn)在我們要翻轉(zhuǎn)區(qū)間[l,r],比方說[4,6],就是圖中綠點的區(qū)間:

我們先想辦法把這個區(qū)間給獨立出來
那么我們先把r+1這個點Splay到根節(jié)點,也就是Splay(7)。



轉(zhuǎn)上來了。

然后再把l-1轉(zhuǎn)到r+1的左兒子,也就是Splay(3,tree[root].c[0])。
畢竟上文說了,可以把一個點splay到它上方任意一個節(jié)點,而它肯定在根節(jié)點的左側(cè),那么根節(jié)點的左兒子一定在它的上方。

現(xiàn)在我們就把要操作的區(qū)間獨立出來了,就是根的左兒子的右子樹。(是一顆樹)
那么現(xiàn)在就可以做很多事情了。

比方說翻轉(zhuǎn),對于這個獨立出來的子樹,要翻轉(zhuǎn)相當(dāng)于交換每個節(jié)點的左右兒子,但是來如果要交換的話,那么就會很麻煩,況且一個區(qū)間被多次翻轉(zhuǎn)之后,很有可能翻轉(zhuǎn)回來,就浪費很多時間空間。

所以打懶標(biāo)記吧。
標(biāo)記一下這個點是否要翻轉(zhuǎn)左右兒子,輸出的時候如果有標(biāo)記就翻轉(zhuǎn)地輸出。
然后每次翻轉(zhuǎn)區(qū)間的時候不需要對整個區(qū)間打標(biāo)記,只需要在最上面的那個點那里打標(biāo)記就行了。
如果要訪問這個區(qū)間里沒有打過標(biāo)記的點,那么必然會訪問剛才打過標(biāo)記的那個“最上面那個點”,那么在訪問那個點的時候就把標(biāo)記下傳給兒子們,接下來訪問某個兒子,訪問這個兒子的時候再下傳給它的兒子……直到我們訪問到要找的那個點,此時它已經(jīng)得到懶標(biāo)記了,而整個過程幾乎沒有浪費時間在給暫時無關(guān)的結(jié)點打標(biāo)記上。

代碼就丟在文藝平衡樹里面吧。

對了對了,因為要訪問l?1\bold {l-1}l?1r+1\bold {r+1}r+1這兩個結(jié)點,所以為了不在翻轉(zhuǎn)區(qū)間[1,x]\bold {[1,x]}[1,x][x,n]\bold {[x,n]}[x,n]的時候爆掉,要在1號點之前加一個-inf,在n號點之后加一個inf,既然這樣那么哪個點對應(yīng)哪個值就一定要想清楚了。

其他區(qū)間操作(以SuperMemo為例)

Your friend, Jackson is invited to a TV show called SuperMemo in which the participant is told to play a memorizing game. At first, the host tells the participant a sequence of numbers, A1,A2,...An{A1, A2, ... An}A1,A2,...An. Then the host performs a series of operations and queries on the sequence which consists:

ADDxyDADD ~x~ y ~DADD?x?y?D: Add D to each number in sub-sequence Ax...Ay{Ax ... Ay}Ax...Ay For example, performing “ADD241ADD ~2 ~4 ~1ADD?2?4?1” on 1,2,3,4,5{1, 2, 3, 4, 5}1,2,3,4,5results in 1,3,4,5,5{1, 3, 4, 5, 5}1,3,4,5,5
REVERSExyREVERSE ~x ~yREVERSE?x?y: reverse the sub-sequence Ax...Ay{Ax ... Ay}Ax...Ay. For example, performing “REVERSE24REVERSE ~2 ~4REVERSE?2?4” on 1,2,3,4,5{1, 2, 3, 4, 5}1,2,3,4,5 results in 1,4,3,2,5{1, 4, 3, 2, 5}1,4,3,2,5
REVOLVExyTREVOLVE ~x ~y ~TREVOLVE?x?y?T: rotate sub-sequence Ax...Ay{Ax ... Ay}Ax...Ay T times. For example, performing “REVOLVE242REVOLVE ~2~ 4 ~2REVOLVE?2?4?2” on 1,2,3,4,5{1, 2, 3, 4, 5}1,2,3,4,5 results in 1,3,4,2,5{1, 3, 4, 2, 5}1,3,4,2,5
INSERTxPINSERT~ x ~PINSERT?x?P: insert P after Ax. For example, performing “INSERT24INSERT~ 2~ 4INSERT?2?4” on 1,2,3,4,5{1, 2, 3, 4, 5}1,2,3,4,5 results in 1,2,4,3,4,5{1, 2, 4, 3, 4, 5}1,2,4,3,4,5
DELETExDELETE ~xDELETE?x: delete Ax. For example, performing “DELETE2DELETE ~2DELETE?2” on 1,2,3,4,5{1, 2, 3, 4, 5}1,2,3,4,5 results in 1,3,4,5{1, 3, 4, 5}1,3,4,5
MINxyMIN ~x~ yMIN?x?y: query the participant what is the minimum number in sub-sequence Ax...Ay{Ax ... Ay}Ax...Ay. For example, the correct answer to “MIN24MIN 2 ~4MIN2?4” on 1,2,3,4,5{1, 2, 3, 4, 5}1,2,3,4,5 is 222
To make the show more interesting, the participant is granted a chance to turn to someone else that means when Jackson feels difficult in answering a query he may call you for help. You task is to watch the TV show and write a program giving the correct answer to each query in order to assist Jackson whenever he calls.

翻譯

寫一個數(shù)據(jù)結(jié)構(gòu)支持六種操作:
ADDxyDADD~ x ~y~ DADD?x?y?D,對于區(qū)間[x,y][x,y][x,y]每個數(shù)都加上DDD
REVERSExyREVERSE ~x~ yREVERSE?x?y,翻轉(zhuǎn)區(qū)間[x,y][x,y][x,y]
REVOLVExyTREVOLVE ~x ~y ~TREVOLVE?x?y?T,這個厲害了,把區(qū)間[x,y][x,y][x,y]里的每個數(shù)在這個區(qū)間里面循環(huán)右移TTT次,舉個例子就是:1,2,3,4,5→5,1,2,3,4→4,5,1,2,3→3,4,5,1,2{1,2,3,4,5}\to {5,1,2,3,4} \to {4,5,1,2,3} \to {3,4,5,1,2}1,2,3,4,55,1,2,3,44,5,1,2,33,4,5,1,2
INSERTxPINSERT~ x ~PINSERT?x?P,在xxx點的后面插入一個值為PPP的點。
DELETExDELETE ~xDELETE?x,刪掉點xxx
MINxyMIN ~x~ yMIN?x?y,求區(qū)間[x,y][x,y][x,y]的最小值。

一個個來

對于ADD操作,先把這個區(qū)間獨立出來,然后打一個加法懶標(biāo)記。
對于REVERSE操作,上面有。
對于REVOLVE操作,聲勢浩大,徒有其表,首先先把T%=(y-x+1);,那么就是把這個區(qū)間的后T個數(shù)移到前面y-x+1-T個數(shù)的前面;那么就是把前y-x+1-T個數(shù)REVERSE,把后T個數(shù)REVERSE,然后再把整個區(qū)間REVERSE就行了。

(這個字體的6寫得像4)
對于INSERT操作,因為它要求在某個點后面插入值,所以先把這個值xxx當(dāng)成一個區(qū)間[x,x][x,x][x,x](數(shù)學(xué)考試這么寫是會被扣分的)把它獨立出來,也就是先把x+1x+1x+1 SplaySplaySplay到根節(jié)點,再把x?1x-1x?1 SplaySplaySplay到根節(jié)點的兒子,那么xxx就在x?1x-1x?1的右兒子,然后再把PPP接上去就是了。
對于DELETE操作,和INSERT一樣,把xxx獨立出來以后直接取下來就是了。
對于MIN操作,因為我們的樹不是按照數(shù)值大小關(guān)系來排序的,所以要額外開一個值來記錄子樹里的最小值,和siz一起push_up。

代碼

普通平衡樹

咕咕咕

文藝平衡樹

咕咕咕

SuperMemo

咕咕咕

Splay的優(yōu)缺點

相較于AVL和Treap,Splay可以少存一個平衡因子。

Splay還有一個很重要的特性,那就是不穩(wěn)定性,可能飚的很快,也可能被神秘卡掉。
所以“在嚴謹場合”不建議使用。

代碼實現(xiàn)比AVL要簡單一些。

參考文章

以下每一篇都比我的這個好:
伸展樹的基本操作與應(yīng)用 IOI2004國家集訓(xùn)隊 楊思雨
https://blog.csdn.net/a_comme_amour/article/details/79382104
https://www.cnblogs.com/cjyyb/p/7499020.html &https://blog.csdn.net/qq_30974369/article/details/77587168(并不詳細地講了spaly為什么會被卡)
https://blog.csdn.net/chenxiaoran666/article/details/81414567
http://www.cnblogs.com/dalt/p/8167168.html(時間復(fù)雜度分析)
https://blog.csdn.net/CABI_ZGX/article/details/82819882 (SuperMemo)
https://blog.csdn.net/DERITt/article/details/50485008 (更多的區(qū)間操作)

總結(jié)

以上是生活随笔為你收集整理的【原创】/Restarting/ Splay树 (普通平衡树 文艺平衡树 bzoj1895 poj 2580 SuperMemo 题解)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。