不需要旋轉,卻能力壓群雄的資料結構——非旋Treap 看完還不會你打我
非旋Treap講解
Treap,一種平衡樹。作為一棵平衡樹,一定是遵從著某種原則,使得這棵樹儘量的接近完全二叉樹。除了二叉搜尋樹都具備的性質——左子樹 ≤ 根 ≤ 右子樹,顧名思義,Treap = tree+heap。這時他的特殊性質就飄出水面了——heap。有一個需要慢慢理解的東西,我覺得是本文最重要的——二叉搜尋樹中順次排列所參考的引數是靈活的,誰說非要按數值??有的題你就得用下標。一、每個node的元素組成
對於一個node,有fix(隨機分配的數,用rand即可),size(這個node所引領的樹的大小,包括自己),剩下的引數視情況而定。這棵樹所包含點的fix值,需要滿足堆的性質,一般情況下我們會使用最小堆(約定俗成)。以bzoj3224為例:struct Treap { Treap *l, *r; int fix, key, size; //fix表示隨機值,key表示數值,size表示以它為根的這棵子樹的大小 Treap(int key_):fix(randad()), key(key_), l(NULL), r(NULL), size(1) {} inline void updata() { //更新樹的大小 size = 1 + (l?l->size:0) + (r?r->size:0); } }*root; typedef pair<Treap*, Treap*> Droot; inline int Size(Treap *x) {return x?x->size:0;}
一定要注意!在改了某節點後,一定要update!
二、基礎的操作
有人會說,既然都不旋轉了,那肯定操作起來特別難。好吧我告訴你們,真的挺難。不過只要認真看完我的文章(老臉一紅)並多多練習,我相信你一定能使用的得心應手。①Split
先下個定義:Split(x, k)表示,把x引導的這一棵樹的前k個節點(順序是按照左子樹 ≤ 根 ≤ 右子樹來劃定的)從樹中分離出來。函式的返回結果是一個節點對,記作(x1, x2),此時x1所引導的樹就是前k個節點,x2引導的樹就是剩下的節點。注意:Split之後,我們已經真真切切的把它們切開了,現在我們的Treap已經不是一個整體了,一定要養成好習慣,Split之後不要忘了再合併起來Droot Split(Treap *x, int k) { if(!x) return Droot(NULL, NULL); Droot y; if(Size(x->l) >= k) { //如果左子樹的節點數能達到k個,則前k個一定都在左子樹中 y = Split(x->l, k); x->l = y.second; //x的左子樹只留下來了不在前k個位置的節點 x->updata(); y.second = x; //此時x這棵樹可以整個拖上去,作為以前不在前k個位置的節點集合 }else { y = Split(x->r, k - Size(x->l) - 1); //要在右子樹搜尋前幾個,連同根節點與左子樹,作為前k個 x->r = y.first; //x的右子樹變為以前的右子樹的前幾個(此時x的size應恰為k) x->updata(); y.first = x; //整個拖上去,作為前k個位置的點的集合 } return y; }
②Merge
繼續下定義:Merge(x,y)表示,把x這棵樹與y這棵樹合併起來(必須滿足的條件:在劃分順序時,x中所有元素都應排在y中的元素的前面。所以我認為Merge最重要的性質在於有序性)。函式的返回結果是一個節點,表示合併後這棵樹的樹根。Treap *Merge(Treap *A, Treap *B) {
if(!A) return B;
if(!B) return A;
if(A->fix < B->fix) { //如果A的隨機數比較小,那他應該居上
A->r = Merge(A->r, B); //而B本就應該排A後面,於是把A的右子樹和B合併,一併作為A的右子樹(注意順序)
A->updata();
return A;
}else { //B居上
B->l = Merge(A, B->l); //把A和B的左子樹合併,一併作為B的左子樹
B->updata();
return B;
}
}
於是,到現在,基本的操作你就都會了。我們來應用一下。看一下bzoj3224,要我們支援的操作有6個,我們一一來分析。①先說查詢一個數的排名。就和普通二叉搜尋樹沒什麼區別的,直接上程式碼。
inline int Getkth(Treap *x, int v) {
//詢問一個數是第幾小
if(!x) return 0;
int ans = 0, temp = (int)2e9+1;
while(x) {
if(v == x->key) temp = min(temp, ans + Size(x->l) + 1);
if(v > x->key) ans+= Size(x->l) + 1, x = x->r;
else x = x->l;
}
return temp==(int)2e9+1?ans:temp;
}
②順便一塊把查詢第幾名是誰說了吧。異曲同工。int Findkth(int k) {
//尋找第k小
Treap *p; p = root;
while(true) {
if(Size(p->l) == k - 1) return p->key;
if(Size(p->l) > k - 1) p = p->l;
else k-= (Size(p->l) + 1), p = p->r;
}
}
③Insert:只加入一個數,於是我們先查詢,在平衡樹中有幾個比x小的(用Getkth,這就是為什麼我先講查排名),記為k,然後把樹分開,把節點放中間,合併。inline void Insert(int v) {
int k = Getkth(root, v);
Droot x = Split(root, k);
Treap *n = new Treap(v);
root = Merge(Merge(x.first, n), x.second);
return ;
}
④Delete:刪除。與insert一樣,分成三段——前k-1個,第k個,後面剩下的。直接合並第1、3段即可。(如果必要,最好隨刪隨清記憶體,這就是指標的好處。但指標的壞處就是極其不可控,容易re)刪記憶體程式碼:void del(Treap *p) {
if(!p) return ;
if(p->l) del(p->l);
if(p->r) del(p->r);
delete p;
}
⑤⑥前驅與後繼,這個很簡單,就是利用查排名函式。inline int pre(Treap *k, int x) {
int ans = (int)-2e9-1;
while(k) {
if(k->key < x) ans = max(ans, k->key), k = k->r;
else k = k->l;
}
return ans;
}
inline neg(Treap *k, int x) {
int ans = (int)2e9+1;
while(k) {
if(k->key > x) ans = min(ans, k->key), k = k->l;
else k = k->r;
}
return ans;
}
再看一個應用,笛卡爾樹。我們知道,加點的複雜度是log級的,批量加點複雜度顯然有一點大(其實我只是想去掉一點常數,log級並沒有那麼大,但是oj也許會卡)。批量加數時,先存進陣列a中,a[0]表示一共有多少數要進樹。
Treap *Build(int *a) {
static Treap *x, *last;
int p = 0;
for(int i = 1; i <= a[0]; ++i) {
x = new Treap(a[i]);
last = NULL;
while(p && sta[p]->fix > x->fix) {
sta[p]->updata();
last = sta[p];
sta[p--] = NULL;
}
if(p) sta[p]->r = x;
x->l = last;
sta[++p] = x;
}
while(p) sta[p--]->updata();
return sta[1];
}
至此,Treap的基本知識已經講完了。如果大家有興趣看一下平衡樹的操作boss題,我推薦bzoj1500維修數列。附上我的維修數列Treap版程式碼:http://paste.ubuntu.com/26194333/
相關推薦
不需要旋轉,卻能力壓群雄的資料結構——非旋Treap 看完還不會你打我
非旋Treap講解Treap,一種平衡樹。作為一棵平衡樹,一定是遵從著某種原則,使得這棵樹儘量的接近完全二叉樹。除了二叉搜尋樹都具備的性質——左子樹 ≤ 根 ≤ 右子樹,顧名思義,Treap = tree+heap。這時他的特殊性質就飄出水面了——heap。有一個需要慢慢理解
webpack-dev-server使用方法,看完還不會的來找我~
記錄下webpack-dev-server的用法. 首先,我們來看看基本的webpack.config.js的寫法 module.exports = { entry: './src/js/index.js', output: {
SQL語句裡面煩人的引號,看完還不懂罵我!!
現在在做一個人事工資管理系統,採用C#+SQL Server2008實現,在看到很多SQL語句後很不解,查到資料如下: string str =//建立查詢字串 "select ID as '編號',employeeID as '員
手把手教你用java實現syslog訊息的收發,學不會你打我嘍!
>大家好,我是道哥,專注於後端java開發,喜歡寫作和分享。如果覺得文章對你有用,那就點個讚唄!如果能轉發那是對道哥最大的支援! ## syslog的定義 >見文知義,syslog,從英文名字上可以看出是指系統日誌。 >以下內容摘自百度百科: Syslog常被稱為系統日誌或系統記錄,是一種
關於 JOIN 耐心總結,學不會你打我係列
現在隨著各種資料庫框架的盛行,在提高效率的同時也讓我們忽略了很多底層的連線過程,這篇文章是對 SQL 連線過程梳理,並涉及到了現在常用的 SQL 標準。 > 其實標準就是在不同的時間,制定的一些寫法或規範。 ## 從 SQL 標準說起 在編寫 SQL 語句前,需要先了解在不同版本的規範,因為隨著版本的變
java執行緒池原理(入門版)——看完還不懂我直播吃香
網上關於java執行緒池的部落格,大多是直接分析ThreadPoolExecutor類的實現,但是他們就像是做中文翻譯一樣,但是很少有講到本質的東西。 這篇部落格從根本出發,看完可以自己實現一個簡單執行緒池。下面正式開始。 一、我們知道,用java建立一條新執行
!!!超簡單 springboot2.0中 單機 quartz yml檔案配置 持久化到資料庫 看完不會你打我
建立表 可到官網下載原始碼 解壓之後。在docs\dbTables檔案下選擇自己所需要的slq檔案。下載地址 新增引用 <!--quartz--> <dependency> &l
delphi安裝pngimage控件,不需要安裝,只需引用就行
-- ons div 菜單 -c home 文件夾 class alt delphi7的pngimage控件如何安裝 20 解壓後的安裝包如圖所示,求高人指點如何把它安到delphi7上,感激不盡 在路徑裏面引用你這個文件夾菜單--tools---library然
企業不需要“好人”,而是需要稱職的人!
幹什麽 而是 原則 價值 綁架 進行 中國 交流 自己的 企業到底需要不需要好人?企業應該從什麽角度評價員工?相信這是很多企業、大多數老板都會遇到的困擾!這幾天和老板朋友交流對幹部員工的看法,不僅老板口頭上少不了對幹部是“好人”的評價,連我也會有“他的確是一個好人”的回應。
光良不好好唱歌,卻“轉行”跑去當程式設計師?
"我自己是一名從事了8年的j a v a全棧開發工程師,辭職目前在做線上教育講師,來了就是我學生,有不懂的可以問我今年4月我花了一個月整理了一套比較系統適合2018年學習的 j a v a資料,從基礎的面向物件到執行緒,j d b c都有整理,送給每一位小夥伴,這裡是學習者聚集地,如果你有興
在jsp頁面中不需要建立,直接使用的物件
內建物件 * 一共有9個: 變數名 真實型別 作用 * pageContext PageContext
程式設計師面試7輪辛苦拿到offer,卻被壓薪資,感覺被坑了!
一小夥工作快3年了,拿到了阿里雲Java開發崗位P6的offer,算HR面一起,加起來有7輪面試了,將近3個月的時間,什麼jvm、多執行緒程式設計、Linux、網路等方面的面試題,直接面試到自己懷疑人生。而自己跟HR談論薪資的時候也是沒有任何底氣,說是22k,結果被HR壓倒了19k,現在拿的都是15
Iterator使用迭代器這個訪問方法,可以讓開發人員不需要了解訪問的容器的底層結構,就可以對容器遍歷 迭代器是輕量級的容器
package com.java.Interview; import java.util.Iterator; import java.util.LinkedList; import java.util.List; public class TestIterato
我不喜歡程式碼,卻為何堅持做程式設計師?
簡介一轉眼,距離自己成為一名碼農,已近一年了。回想當初,剛成為猿類時的那種對程式碼的激情,已經消失的差不多了。這個可能也跟我當初的想法有關,本身是數學科班出身,又接受了培訓,結果剛找工作時,誤打誤撞進入了售前的行業,寫程式碼就成了自己的未竟事件。我原本的職業規劃是:前端程式設
SpringBoot+jpa配置自動建立表不報錯,卻不建立表
原因 找了網上很多答案,均不對,包括以下幾種: 包導的不對 配置檔案不對 註解寫的不對 … 最後發現原因: Sprint的入口檔案在子目錄裡了,應該比其他諸如server、dao、domain高一級。 例如:service檔案所在為com.wds.met
hexo next主題深度優化(十),博文加密,不需要外掛,極簡模式,相對安全,融合pjax。
如果想自定義功能樣式的往下面看看也許會有點收穫,為了避免讀者不耐煩的看我的廢話,所以移到了下面。 本人部落格:mmmmmm.me 效果: 程式碼: /blog/themes/next/layout/_layout.swig,找到main標籤在吐下程式碼處新增自定義的sw
Container內不需要OS,為何需要OS的基礎映象?
轉載:http://dockerone.com/question/6 首先我來回答一下問題一,Container內需不需要OS?Container不是一個VM技術,所以和OS沒有關係。如果我沒有理解錯,這個Container應該指的是Docker Run出的執行環境,因為
pl/sql developer不需要選中,執行游標所在行
問題: 執行某一行語句時,需要選中改行語句,按F8才能執行; 解決辦法: 工具——》首選項——》SQL視窗——》自動選擇該語句。這樣游標放在此行,按F8就可以執行,需要注意的是sql語句必須以分號結束。
高德地圖的Marker不需要setMap,建立時預設就可以顯示在地圖上
很驚奇,之前用百度地圖API都是需要在建立Marker物件後,再執行setMap函式,才能顯示到地圖上,高德完全可以省略這一步,程式碼如下: $(document).ready(function(){ // 為防止CSRF(Cross-s
csdn快速的轉載別人部落格裡的文章,不需要複製,簡單一點
對於喜歡逛CSDN的人來說,看別人的部落格確實能夠對自己有不小的提高,有時候看到特別好的部落格想轉載下載,但是不能一個字一個字的敲