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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

[非旋平衡树]fhq_treap概念及模板,例题:普通平衡树,文艺线段树

發(fā)布時間:2023/12/3 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [非旋平衡树]fhq_treap概念及模板,例题:普通平衡树,文艺线段树 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

  • 概念
  • 全套模板
    • push_up模板
    • split拆樹模板(按權(quán)值拆)
    • split拆樹模板(按個數(shù)拆)
    • merge合并模板(地址版)
    • merge合并模板(帶返回根)
    • 區(qū)間模板
    • insert插入模板
    • delete刪除模板
    • find_kth找第k大模板
    • get_rank找排名模板
    • pre找前驅(qū)模板
    • suf找后驅(qū)模板
  • 例題1:普通平衡樹
    • 題目
    • 代碼實現(xiàn)
  • 例題2:文藝線段樹
    • 題目
    • 代碼實現(xiàn)

建議在看這篇博客之間要了解一下帶旋Treap
我會在模板前面寫上一部分的思路講解,幫助各位理解

概念

根據(jù)它的名字我們也可以得知,這種數(shù)據(jù)結(jié)構(gòu)就是treaptreaptreap的后代,只不過不帶旋轉(zhuǎn),其余都是一致的
所以在運用和代碼上會有所異同。它比treaptreaptreap多了splitsplitsplit(拆樹)和mergemergemerge(合并)操作,所以得到的結(jié)果是可以多處理數(shù)據(jù)結(jié)構(gòu)的區(qū)間問題。以一換一
接下來我們就重點介紹splitsplitsplitmergemergemerge還有區(qū)間操作到底是個什么玩意兒???

全套模板

因為是自己修改后的模板,可能會有不嚴(yán)謹(jǐn)處,歡迎大家指出并更正!

先照樣介紹各個數(shù)組變量的含義:
SizeSizeSize:表示節(jié)點數(shù)量也可作最后一個點編號
cnt[p]cnt[p]cnt[p]:表示編號為ppp,值為xxxtreaptreaptreap中插入的次數(shù)
key[p]key[p]key[p]:表示該點ppp的值為xxx
rd[p]rd[p]rd[p]:就是我們自己搞的修正值,用rand()rand()rand()函數(shù)隨機生成
siz[p]siz[p]siz[p]:編號為ppp的子樹包括本身在內(nèi)的節(jié)點數(shù)量即大小
son[p][2]son[p][2]son[p][2]son[p][0]son[p][0]son[p][0]表示p的左兒子,son[p][1]son[p][1]son[p][1]表示ppp的右兒子


push_up模板

先蓄蓄力,放松放松

void push_up ( int x ) {siz[x] = siz[son[x][0]] + siz[son[x][1]] + cnt[x]; }

split拆樹模板(按權(quán)值拆)

splitsplitsplit拆樹的結(jié)果就是把樹根據(jù)要求值kkk拆成兩半

左邊全是值≤k≤kk的點,右邊全是值>k>k>k的點

上圖講解:充分運用畫過的圖,我?guī)ьI(lǐng)大家走一遍,再不懂就不管本蒟蒻了

假設(shè)我們的kkk為35,那么首先從根節(jié)點1開始,發(fā)現(xiàn)1的權(quán)值25小于35

這個時候我們就能確定根節(jié)點以及根節(jié)點的左子樹的權(quán)值全都是小于35的

那么這個時候它們是屬于拆分后左邊的子樹

但是我們會發(fā)現(xiàn)根節(jié)點的右子樹也存在可能值大于35的節(jié)點

我們就需要繼續(xù)往下拆分

接下來走到節(jié)點3,發(fā)現(xiàn)權(quán)值大于35,可以得出的結(jié)論是3節(jié)點以及它的右子樹的權(quán)值都是大于35的,應(yīng)該是屬于拆分后的右子樹

但是同樣的我們不能肯定它的左兒子是否也是歸屬于右邊,繼續(xù)往左拆分

最后走到了葉子節(jié)點,發(fā)現(xiàn)節(jié)點4的權(quán)值小于等于35也應(yīng)該歸于左邊

這個時候就把節(jié)點4接到根節(jié)點1的右邊,成功把1和3的邊給斷掉

最后一層一層回溯,最頂層的兩個根節(jié)點就分別為1,3

節(jié)點1統(tǒng)領(lǐng)了所有權(quán)值小于等于kkk的子樹,節(jié)點3統(tǒng)領(lǐng)了所有權(quán)值大于kkk的子樹

我的寫法是傳地址,這樣就直接更改了


void split ( int p, int &l, int &r, int x ) {if ( ! p ) {l = r = 0;return;}if ( key[p] <= x ) {l = p;split ( son[p][1], son[p][1], r, x );push_up ( l );}else {r = p;split ( son[p][0], l, son[p][0], x );push_up ( r );} }

split拆樹模板(按個數(shù)拆)

此代碼有適用范圍!!!在某些題中會出錯

按下標(biāo)拆的思路與按權(quán)值拆是一樣的,只不過往右子樹找的時候記得把左子樹和根占得位置給減掉即可

拆出來的左子樹的個數(shù)恰好是給定的kkk,右子樹就是剩下來的所有點

void split_id ( int p, int &l, int &r, int x ) {if ( ! p ) {l = r = 0;return;}if ( siz[son[p][0]] + 1 <= x ) {l = p;split_id ( son[p][1], son[p][1], r, x - siz[son[p][0]] - 1 );push_up ( l );}else {r = p;split_id ( son[p][0], l, son[p][0], x );push_up ( r );} }

merge合并模板(地址版)

我們可以發(fā)現(xiàn)拆分子樹的時候,改變了樹的形態(tài),這也是無法進行treaptreaptreap的旋轉(zhuǎn)操作的一個原因,
百因必有果,你的報應(yīng)就是我

既然方便了splitsplitsplit拆分,改變了樹的形態(tài),我們就必須再寫一個補丁函數(shù),把樹進行還原修復(fù)

但是我們不再是使用權(quán)值kkk進行,我們思考treaptreaptreap用旋轉(zhuǎn)的目的是為了維護樹的鍵值不是從大到小就是從小到大

反正就是要有一定的順序

那么mergemergemerge的目的也是維護樹的鍵值有順序

本來splitsplitsplit拆的樹也是我們維護好了順序的

所以mergemergemerge合并的時候根據(jù)鍵值順序來合并,也能還原splitsplitsplit所拆的樹

在這里我仍然選擇的傳地址直接改在原來的地方,如果把上邊的splitsplitsplit理解了,那么我相信這個也就很好理解了

void merge ( int &p, int x, int y ) {if ( ! x || ! y ) {p = x + y;return;}if ( rd[x] < rd[y] ) {p = x;merge ( son[p][1], son[p][1], y );}else {p = y;merge ( son[p][0], x, son[p][0] );}push_up ( p ); }

merge合并模板(帶返回根)

int merge ( int x, int y ) {if ( ! x || ! y )return x + y;if ( rd[x] < rd[y] ) {son[x][1] = merge ( son[x][1], y );push_up ( x );return x;}else {son[y][0] = merge ( x, son[y][0] );push_up ( y );return y;} }

區(qū)間模板

其實就是先把這個區(qū)間[l,r][l,r][l,r]拆出來然后搞一波,再把它合并回去

可以理解為先把部隊里某一個方陣的士兵扯出來再捅幾刀最后再讓他們歸隊,好殘忍

void XXX ( int x, int y ) {int l, r, L, R;spilt ( root, l, r, y );split ( l, L, R, x - 1 );//區(qū)間里面進行的操作merge ( l, L, R );merge ( root, l, r ); }

我們以翻轉(zhuǎn)reversereversereverse為例,小聲bb:是為了讓你們做文藝平衡樹更簡單

void reverse ( int x, int y ) {int l, r, L, R;spilt ( root, l, r, y );split ( l, L, R, x - 1 );lazy[R] = !lazy[R];//對[x,y]區(qū)間進行打標(biāo),1表示翻轉(zhuǎn),0表示沒有翻轉(zhuǎn) merge ( l, L, R );merge ( root, l, r ); }

簡單過渡一下:其實多做幾道題多用用模板會對代碼更加理解,為了方便各位理解下面更改的函數(shù),在這里簡單總結(jié)一下splitsplitsplitmergemergemerge的思路

split(root,l,r,x)split(root,l,r,x)split(root,l,r,x)表示把以rootrootroot為根的子樹按照權(quán)值xxx拆分,lll存儲著小于等于xxx的子樹的根,rrr存儲著大于xxx的子樹的根

merge(root,l,r)merge(root,l,r)merge(root,l,r)表示把一棵子樹的根為lll和另一棵子樹的根為rrr合并為一棵根為rootrootroot的新根

那么其余的操作都可以用splitsplitsplitmergemergemerge改變我們以前的寫法,新朋友就要多用用嘛!


insert插入模板

insertinsertinsert之前我們是用的遞歸方式,在這里就要充分運用splitsplitsplitmergemergemerge

我聲明一下,很多很多篇博客都是直接新建一個節(jié)點,本蒟蒻就不理解了,對于一個點它可能已經(jīng)出現(xiàn)在樹上了,這個時候就直接cnt++cnt++cnt++,為什么要選擇新建點呢?

所以我就費了九牛二虎之力寫出了自己想要的模板

當(dāng)然對于某部分的題各個點之間是互不相同的,或其它特殊的要求,我的代碼就與大佬們成為一流的了,這個時候就可以刪掉我代碼中if的判斷即可,不刪也不影響,最多代碼長了一丟丟而已啦~

  • 首先我們把樹先拆成權(quán)值都≤x≤xx的子樹和權(quán)值都>x>x>x的子樹
  • 再把權(quán)值≤x≤xx的子樹拆分成權(quán)值≤x?1≤x-1x?1的樹和權(quán)值>x?1>x-1>x?1也就是權(quán)值等于xxx的樹
  • 接著我們就判斷儲存權(quán)值等于xxx的樹的節(jié)點是否為空,
    • 如果為空就意味著樹上并沒有該點,就新建一個點;
    • 否則就直接cnt++cnt++cnt++updateupdateupdate一下
  • 拆了就要合并,我們怎么拆的就怎么倒著并回去,很簡單的,本蒟蒻都能自己打出來
void insert ( int x ) {int l, r, L, R;split ( root, l, r, x );split ( l, L, R, x - 1 );if ( R ) {cnt[R] ++;push_up ( R );merge ( l, L, R );merge ( root, l, r );}else {++ Size;cnt[Size] = siz[Size] = 1;rd[Size] = rand ();key[Size] = x;merge ( l, L, Size );merge ( root, l, r );} }

delete刪除模板

仿照insertinsertinsert的思路

  • 先把值為xxx的這個點拆出來
  • 接下來判斷如果這個點插入的次數(shù)是否大于1
    • 如果大于可以直接cnt??cnt--cnt??,該點不會消失,倒著合并回去;
    • 否則該點就應(yīng)該消失在樹上,我們可以通過不讓它參與合并,排擠它 ,那么它就不會出現(xiàn)在樹上了,直接把值小于等于x?1x-1x?1的樹和值大于xxx的樹合并即可
void delet ( int x ) {int l, r, L, R;split ( root, l, r, x );split ( l, L, R, x - 1 );if ( R && cnt[R] > 1 ) {cnt[R] --;push_up ( R );merge ( l, L, R );merge ( root, l, r );}elsemerge ( root, L, r ); }

剩下的查找其實是可以照搬的,但是我還是給大家分享一些其它的寫法吧!!


find_kth找第k大模板

這個我還是很喜歡這種寫法的,所以就不更改了

int find_kth ( int rt, int x ) {if ( siz[son[rt][0]] >= x )return find_kth ( son[rt][0], x );else if ( siz[son[rt][0]] + cnt[rt] < x )return find_kth ( son[rt][1], x - siz[son[rt][0]] - cnt[rt] );elsereturn key[rt]; } //非遞歸結(jié)構(gòu)體版本 ↓ void find_val( int x ) {int now = rt;while( 1 ) {if( x <= t[t[now].lson].siz ) now = t[now].lson;else if( x <= t[t[now].lson].siz + t[now].cnt ) break;else x -= ( t[t[now].lson].siz + t[now].cnt ), now = t[now].rson;}printf( "%d\n", t[now].val ); }

Upd:
下面求排名為 xxx 的數(shù)的方法不一定是對的。
因為按照個數(shù)大小分裂代碼的正確性當(dāng)且僅當(dāng)數(shù)據(jù)中每個數(shù)互不相等。
顯然,設(shè)想某個數(shù)有若干個,占據(jù)了排名為一段的區(qū)間,如果按照 x/x?1x/x-1x/x?1 的個數(shù)分,全都劃在該數(shù)身上,則 RRR 就是個空子樹了。
如果直接判 RRR 是否為空也是錯誤的。
但是我也不知道為什么??!!所以還是麻煩大家寫上面的方法。
也有可能是因為博主的其它模板某些限制把。。。
數(shù)據(jù)結(jié)構(gòu)真是一個比一個玄學(xué)!!凸(艸皿艸 )

void find_val( int x ) {int l, r, L, R;split_siz( rt, x, l, r );split_siz( l, x - 1, L, R );printf( "%d\n", t[R].val );rt = merge( merge( L, R ), r ); }

get_rank找排名模板

我們就充分運用新學(xué)函數(shù),思考一下如果把≤x?1≤x-1x?1的樹拆出來
那么它的大小+1+1+1是不是就是xxxrankrankrank排名呢!!!實在是

void get_rank ( int x ) {int l, r;split ( root, l, r, x - 1 );printf ( "%d\n", siz[l] + 1 );merge ( root, l, r ); }

pre找前驅(qū)模板

找前驅(qū),這里是嚴(yán)格小于的情況,先拆分一下看有木有權(quán)值小于xxx的點
有的話我們就調(diào)用findfindfind_kthkthkth在拆分出來的那棵子樹中去找最后一個也就是xxx的前一個

int pre ( int x ) {int l, r, result;split ( root, l, r, x - 1 );if ( siz[l] )result = find_kth ( l, siz[l] );elseresult = INF;merge ( root, l, r );return result; }

suf找后驅(qū)模板

找后驅(qū),與找前驅(qū)相似,這里是嚴(yán)格大于的情況,先拆分一下看有木有權(quán)值大于xxx的點
有的話我們就調(diào)用findfindfind_kthkthkth在拆分出來的那棵子樹中去找第一個也就是xxx的后一個

int suf ( int x ) {int l, r, result;split ( root, l, r, x );if ( siz[r] )result = find_kth ( r, 1 );elseresult = INF;merge ( root, l, r );return result; }

Upd:當(dāng)然你可以直接暴力的裂開。以找前驅(qū)為例,把 ≤x?1\le x-1x?1 的子樹列出來,從子樹的根開始瘋狂走右兒子(如果有)。

void find_pre( int x ) {int l, r;split_val( rt, x - 1, l, r );int now = l;while( t[now].rson ) now = t[now].rson;printf( "%d\n", t[now].val );rt = merge( l, r ); }void find_suf( int x ) {int l, r;split_val( rt, x, l, r );int now = r;while( t[now].lson ) now = t[now].lson;printf( "%d\n", t[now].val );rt = merge( l, r ); }

老套路來些題目練習(xí)練習(xí),實在是太模板了,直接器官移植都能過,哎╮(╯▽╰)╭

例題1:普通平衡樹

題目

點擊查看

代碼實現(xiàn)

一樣一樣的,進行器官移植即可

#include <cstdio> #include <algorithm> using namespace std; #define MAXN 100005 #define INF 0x7f7f7f7f int root, n, Size; int son[MAXN][2], cnt[MAXN], siz[MAXN], rd[MAXN], key[MAXN];void push_up ( int x ) {siz[x] = siz[son[x][0]] + siz[son[x][1]] + cnt[x]; }void split ( int p, int &l, int &r, int x ) {if ( ! p ) {l = r = 0;return;}if ( key[p] <= x ) {l = p;split ( son[p][1], son[p][1], r, x );push_up ( l );}else {r = p;split ( son[p][0], l, son[p][0], x );push_up ( r );} }void merge ( int &p, int x, int y ) {if ( ! x || ! y ) {p = x + y;return;}if ( rd[x] < rd[y] ) {p = x;merge ( son[p][1], son[p][1], y );}else {p = y;merge ( son[p][0], x, son[p][0] );}push_up ( p ); }void insert ( int x ) {int l, r, L, R;split ( root, l, r, x );split ( l, L, R, x - 1 );if ( R ) {cnt[R] ++;push_up ( R );merge ( l, L, R );merge ( root, l, r );}else {++ Size;cnt[Size] = siz[Size] = 1;rd[Size] = rand ();key[Size] = x;merge ( l, L, Size );merge ( root, l, r );} }void delet ( int x ) {int l, r, L, R;split ( root, l, r, x );split ( l, L, R, x - 1 );if ( R && cnt[R] > 1 ) {cnt[R] --;push_up ( R );merge ( l, L, R );merge ( root, l, r );}elsemerge ( root, L, r ); }int find_kth ( int rt, int x ) {if ( siz[son[rt][0]] >= x )return find_kth ( son[rt][0], x );else if ( siz[son[rt][0]] + cnt[rt] < x )return find_kth ( son[rt][1], x - siz[son[rt][0]] - cnt[rt] );elsereturn key[rt]; }int pre ( int x ) {int l, r, result;split ( root, l, r, x - 1 );if ( siz[l] )result = find_kth ( l, siz[l] );elseresult = INF;merge ( root, l, r );return result; }int suf ( int x ) {int l, r, result;split ( root, l, r, x );if ( siz[r] )result = find_kth ( r, 1 );elseresult = INF;merge ( root, l, r );return result; }void get_rank ( int x ) {int l, r;split ( root, l, r, x - 1 );printf ( "%d\n", siz[l] + 1 );merge ( root, l, r ); }int main() {scanf ( "%d", &n );while ( n -- ) {int opt, x;scanf ( "%d %d", &opt, &x );switch ( opt ) {case 1 : insert ( x ); break;case 2 : delet ( x ); break;case 3 : get_rank ( x ); break;case 4 : printf ( "%d\n", find_kth ( root, x ) ); break;case 5 : printf ( "%d\n", pre ( x ) ); break;case 6 : printf ( "%d\n", suf ( x ) ); break;}}return 0; }

例題2:文藝線段樹

題目

點擊查看題目

代碼實現(xiàn)

在這里因為涉及到一個區(qū)間翻轉(zhuǎn)問題,我們就可以類比線段樹打lazylazylazy標(biāo)記,也對treaptreaptreap樹打一個標(biāo)記
那么在我們進行split,mergesplit,mergesplit,merge操作時,要保證對于一個點,它的左兒子和右兒子是對的,所以這里要寫一個標(biāo)記下放的pushdownpushdownpushdown
最后輸出數(shù)列的時候也采用遞歸的方式,左中右的中序遍歷,在這之間順便進行標(biāo)記下放

#include <cstdio> #include <iostream> #include <algorithm> using namespace std; #define MAXN 100005 #define INF 0x7f7f7f7f int root, n, Size, m; int son[MAXN][2], cnt[MAXN], siz[MAXN], rd[MAXN], key[MAXN]; bool lazy[MAXN];void push_up ( int x ) {siz[x] = siz[son[x][0]] + siz[son[x][1]] + cnt[x]; }void pushdown ( int x ) {if ( x && lazy[x] ) {swap ( son[x][0], son[x][1] );lazy[son[x][0]] = !lazy[son[x][0]];lazy[son[x][1]] = !lazy[son[x][1]];lazy[x] = 0;return; } }void split ( int p, int &l, int &r, int x ) {if ( ! p ) {l = r = 0;return;}pushdown ( p );//千萬不要放在if-else里面,先把標(biāo)記下放去交換左右兒子//確保此時p的左右兒子是真的,不然就報錯了/(ㄒoㄒ)/~~if ( siz[son[p][0]] + 1 <= x ) {l = p;split ( son[p][1], son[p][1], r, x - siz[son[p][0]] - 1 );push_up ( l );}else {r = p;split ( son[p][0], l, son[p][0], x );push_up ( r );} }void merge ( int &p, int x, int y ) {if ( ! x || ! y ) {p = x + y;return;}if ( rd[x] < rd[y] ) {pushdown ( x );p = x;merge ( son[p][1], son[p][1], y );}else {pushdown ( y );p = y;merge ( son[p][0], x, son[p][0] );}push_up ( p ); }void insert ( int x ) {int l, r, L, R;split ( root, l, r, x );split ( l, L, R, x - 1 );if ( R ) {cnt[R] ++;push_up ( R );merge ( l, L, R );merge ( root, l, r );}else {++ Size;cnt[Size] = siz[Size] = 1;rd[Size] = rand ();key[Size] = x;merge ( l, L, Size );merge ( root, l, r );} }void delet ( int x ) {int l, r, L, R;split ( root, l, r, x );split ( l, L, R, x - 1 );if ( R && cnt[R] > 1 ) {cnt[R] --;push_up ( R );merge ( l, L, R );merge ( root, l, r );}elsemerge ( root, L, r ); }void print ( int x ) {if ( ! x )return;pushdown ( x );print ( son[x][0] );printf ( "%d ", key[x] );print ( son[x][1] ); }void reverse ( int x, int y ) {int l, r, L, R;split ( root, l, r, y );split ( l, L, R, x - 1 );lazy[R] = !lazy[R];merge ( l, L, R );merge ( root, l, r ); }int main() {scanf ( "%d %d", &n, &m );for ( int i = 1;i <= n;i ++ )insert ( i );for ( int i = 1;i <= m;i ++ ) {int idl, idr;scanf ( "%d %d", &idl, &idr );reverse ( idl, idr );}print ( root );return 0; }

因此這道題啟示我們,隨著我們的操作要求的不一樣,在splitsplitsplitmergemergemerge中一些語句可能會發(fā)生順序變換,不能盲目地去背模板,一定要理解

可能會有部分代碼細(xì)節(jié)錯誤,因為這些題實在是水,導(dǎo)致有些寫錯的代碼還是能跑過數(shù)據(jù)


總結(jié)

以上是生活随笔為你收集整理的[非旋平衡树]fhq_treap概念及模板,例题:普通平衡树,文艺线段树的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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