洛谷P3369 模板 普通平衡樹

2021-08-21 15:19:29 字數 4080 閱讀 5981

本蒟蒻最近剛剛學會平衡樹,特來寫篇部落格以加深印象。

(我的意思是若寫的不好望各位奆佬多多包含!)

插入 x 數

刪除 x 數(若有多個相同的數,因只刪除乙個)

查詢 x 數的排名(排名定義為比當前數小的數的個數 +1 。若有多個相同的數,因輸出最小的排名)

查詢排名為 x 的數

求 x 的前驅(前驅定義為小於 x ,且最大的數)

求 x 的後繼(後繼定義為大於 x ,且最小的數)

輸入格式:

第一行為 n ,表示操作的個數,下面 n 行每行有兩個數 opt 和 x , opt 表示操作的序號

輸出格式:

對於操作 3,4,5,6 每行輸出乙個數,表示對應答案

輸入樣例#1:

10

1 106465

4 11 317721

1 460929

1 644985

1 84185

1 89851

6 81968

1 492737

5 493598

輸出樣例#1:

106465

84185

492737

樣例是真的大啊!=_=

大的都不像是樣例了!

好了,現在開始講解!

首先講解我用的變數吧!!

int ch[maxn][2] ;//存兒子,ch[0]存左兒子,ch[1]存右兒子

int f[maxn] ;//存爹

int size[maxn] ;//存這個點的子樹大小

int cnt[maxn] ;//存權值

int key[maxn] ;//存數值

int sz,root ;//整棵樹的大小和樹根

首先,一切從簡單的開始講起,首先說輸入(不想看的向下翻)

首先輸入乙個數n,然後下面n行,每行輸入乙個操作標記和數。

int n,opt,x;

scanf("%d",&n);

for (int i=1;i<=n;++i)

}

然後在依次講解操作

操作1:插入

以我之見,插入這個是最容易打錯的操作(我就打錯了好幾次)。 

總體的思路是:如果遇到乙個與x數值相同的數,就把數的權值加一。

還有一些特殊的情況:

1.如果插入時樹為空樹:那麼就把整棵樹的長度加一,因為只有乙個點,所以要給他的左右兒子賦值為0 ;

2.如果插入時已經到了樹的最底端(也沒有找到),那麼就可以直接插入。

具體見**,**如下:

void insert(int x)

int now = root , fa = 0 ;

while(true)

fa = now ;

if(key[now] < x) now = ch[now][1] ;//如果數值比當前點的數字大,找右兒子

else now = ch[now][0] ;//反之,找左兒子

if(now == 0)

}}

操作2:刪除

刪除在我看來無異於大模擬 ,也許就是個大模擬吧!!

刪除,簡單來說,就是把這乙個節點的data(兒子,父親,祖宗)給clear(清除)掉

但是,這個還是分好多好多的情況的啊!

1.如果有不少相同的數,那麼就隨便踢掉乙個

2.如果要刪得數沒有孩子,那麼就直接刪掉(畢竟沒有孩子沒有多餘的牽掛啥的)

3.如果只有乙個獨生子,那麼就要學一下檀黎鬥神的操作,乾掉自己的爸爸,自己當爸爸!!

最後捏,我們只要改變各個父子關係,好讓要刪數的data銷聲匿跡就好了!

然後就是**:

void del(int x)

if(!ch[root][0] && !ch[root][1])

if(!ch[root][0])

if(!ch[root][1])

int leftbig = pre() , oldroot = root ;找到新根(數的前驅),然後儲存舊根

splay(leftbig) ;//讓新根當根

ch[root][1] = ch[oldroot][1] ;//把原來x的右兒子接到新根上。

f[ch[oldroot][1]] = root ;//這樣就吧x徹底刪了qwq

clear(oldroot) ;//刪除廢物資料

update(root) ;//更新資料

}

ps:clear和pre也是個函式,意思很常規,後面有**。

操作3:查詢x排名

如果x比當前的節點小,那麼就去左子樹找

反之,去右子樹找

如果相等的話怎麼辦??

如果相等的話就停下啊還怎麼辦!!

好吧,不皮了

int find(int x)

ans += cnt[now] ;

now = ch[now][1] ;

} }}

操作4:找到排名為x的節點

這個說實話煩人啊!操作3和操作4看的我是眼花繚亂啊!qwq~~我太弱了~~qwq  t~t 

和上面的思想差不多~~

如果當前點有左子樹,且x比左子樹的權值(占得排名)小的話,就搭乘前往左子樹的列車。

反之,就去右子樹。

int findx(int x)

}}

操作5:找前驅

操作6:找後繼(狠心遺棄)

看了這麼久,大傢伙會發現,右子樹》根》左子樹的小規律,所以,前驅就是最左邊的點 ,同理,後繼就是最右邊的點嘍!!

int pre()
好了,我們現在基本操作都講完了,現在該敘述一下中間涉及到的函式了

function1:clear(int x)

只用於將點刪除後的清零小工作~~

void clear(int x)
很簡單,沒啥好講的

function2:get(int x)

作用就是查詢x是左兒子還是右兒子。

int get(int x)

/*講解一下:(其實都能看懂吧~~)

如果x爹的右兒子是x的話,就返回1

如果x爹的右兒子不是x的話,也就是x是x爹的做兒子的話,返回0

*/

function3 :update(int x )

唯一用途:修改後更新數值

code

void update(int x)

}

也比較簡單,於是就不住釋了~~~

function4:rotate(int x)

這個可是平衡樹的重點大戲

rotate沒理解好就和輸出輸出錯了乙個效果!!qwq、

具體思路:首先找到x關於x爹的關係,記做k關係(用get())

然後 將x與k關係相反的兒子 的父親 記做與(x爹)關係相同的兒子

然後再將和x關係相反的兒子  的父親  記做  x爹 的父親

將x爹的父親記做x

將x與k關係相反的兒子  記做  x爹

將x的父親記做x爹的父親(也就是x的爺爺)

有點混亂是不是呀??!!

正常,看看**,自己畫畫圖就好了!!!

void rotate(int x)

update(old) ;update(x) ;//更新數字

}

function5 : splay(int x)

就是多次的rotate嘍!

直接看《code》 吧

void splay(int x)

} root = x ;

}

完結散花!!!! 

啥??完整**??

把上面組合一下應該就是了吧……………………

洛谷P3369 模板 普通平衡樹

插入x數 刪除x數 若有多個相同的數,因只刪除乙個 查詢x數的排名 排名定義為比當前數小的數的個數 1。若有多個相同的數,因輸出最小的排名 查詢排名為x的數 求x的前驅 前驅定義為小於x,且最大的數 求x的後繼 後繼定義為大於x,且最小的數 輸入格式 第一行為n,表示操作的個數,下面n行每行有兩個數...

洛谷P3369 模板 普通平衡樹 Treap

插入xxx數 刪除x xx數 若有多個相同的數,因只刪除乙個 查詢x xx數的排名 排名定義為比當前數小的數的個數 1 1 1。若有多個相同的數,因輸出最小的排名 查詢排名為x xx的數 求x xx的前驅 前驅定義為小於x xx,且最大的數 求x xx的後繼 後繼定義為大於x xx,且最小的數 總算...

題解 洛谷 P3369 模板 普通平衡樹

splay是像我這樣的小蒟蒻一開始學的平衡樹。雖然splay常數不小,但是功能十分全面,既可以當區間樹也可以當平衡樹 當然這兩者不可兼顧 看到題解裡一堆dalao寫陣列,但是splay本來是應該用指標實現的 據說這樣常數會小很多 於是蒟蒻就開始寫起了指標splay。出人意料,陣列splay我一遍a,...