模板 樹套樹

2022-04-30 18:30:09 字數 4039 閱讀 4401

【xsy2685】【lg3380】【bzoj3196】【tyvj1730】二逼平衡樹

1.查詢k在區間內的排名(乙個數的排名是小於這個數的個數+1)

2.查詢區間內排名為\(k\)的值

3.修改某一位值上的數值

4.查詢\(k\)在區間內的前驅(前驅定義為小於\(x\),且最大的數)

5.查詢\(k\)在區間內的後繼(後繼定義為大於\(x\),且最小的數)

第一行兩個數\(n,m\)表示長度為\(n\)的有序序列和\(m\)個操作

第二行有\(n\)個數,表示有序序列

下面有\(m\)行,\(opt\)表示操作標號

若\(opt=1\)則為操作\(1\),之後有三個數\(l,r,k\)表示查詢\(k\)在區間\([l,r]\)的排名

若\(opt=2\)則為操作\(2\),之後有三個數\(l,r,k\)表示查詢區間\([l,r]\)內排名為\(k\)的數

若\(opt=3\)則為操作\(3\),之後有兩個數\(pos,k\)表示將\(pos\)位置的數修改為\(k\)

若\(opt=4\)則為操作\(4\),之後有三個數\(l,r,k\)表示查詢區間\([l,r]\)內\(k\)的前驅

若\(opt=5\)則為操作\(5\),之後有三個數\(l,r,k\)表示查詢區間\([l,r]\)內\(k\)的後繼

對於操作\(1,2,4,5\)各輸出一行,表示查詢結果

9 64 2 2 1 9 4 0 1 1

2 1 4 3

3 4 10

2 1 4 3

1 2 5 9

4 3 9 5

5 2 8 524

3491.\(n\)和\(m\)的資料範圍:\(n,m≤50000\)

2 序列中每個數的資料範圍:\([0,108]\)

3.雖然原題沒有,但事實上\(5\)操作的\(k\)可能為負數

4.保證答案一定存在

給你乙個有序序列,需要實現:

區間查詢\(k\)排名

區間查詢排名\(k\)的值

單點修改權值

區間查詢\(k\)前驅

區間查詢\(k\)後繼

這道題,涉及到了區間查詢以及單點修改,我們反應過來:這是一道待修區間k大的題目。

我們可愛的線段樹能夠實現單點修改和區間修改,但是面對區間k大就無能為力

我們精簡的主席樹和功能強大的平衡樹可以完成區間k大的任務,但是不能完成待修的任務

於是我們不得不放棄普通的線段樹或者平衡樹做法,這時,就要用樹套樹做法了。

我們在外層建一棵線段樹,完成單點修改的任務

再在每個線段樹節點建一棵平衡樹(可以是\(treap\),也可以是\(splay\),也可以是另外各種可以實現區間詢問的平衡樹)

每次區間詢問的時候,就呼叫線段樹節點中的平衡樹完成查詢,在這個時候,平衡樹對應的就是乙個區間,而不是一整個線段樹

在這裡我用的是\(fhq\)

\(treap\),個人認為\(fhq\)

\(treap\)容易理解,好打

在這裡就不多講\(fhq\)

\(treap\) 的**部分,都是板子,就只講線段樹的部分

為了方便,我將\(fhq\)

\(treap\)封裝成乙個\(struct\),需要的可以看看

然後我們考慮一下,如果每次都查詢的是小於等於\(k\)的數的個數,那麼合併的時候,可能會出現很多個等於\(k\)的值,也就是說,這樣求出的排名可能會重複多出一些

於是我們將\(queryrank\)定義為尋找小於\(k\)的數的個數,在最後的時候再\(+1\)

在這裡,我們看看線段樹在遍歷的時候的小細節,我們可以分為\(3\)類

當整個區間都在左子樹,返回左子樹的查詢

當整個區間都在右子樹,返回右子樹的查詢

區間橫跨左右子樹,返回兩個子樹的查詢和,不過左子樹的查詢區間要變成\([ql,mid]\),右子樹變成\([mid+1,qr]\)

int queryrank(int k,int l,int r,int ql,int qr,int val)//詢問在[ql,qr]區間內小於val的數的個數

這個操作不能樹套樹中實現,因為不知道怎麼將答案合併起來,於是我們考慮寫乙個簡單的二分答案

每次二分乙個值,用\(queryrank\)(樓上)來查詢這個值的\(rank\)

//無腦二分不多解釋

int queryval(int ql,int qr,int val)//詢問在[ql,qr]區間內排名為val的值

return ans;

}

我們從根往\([k,k]\)遍歷,將路徑上的節點中的平衡樹中的\(k\)位置上的值先刪除,然後再加入新的值,直到遍歷到\([k,k]\),跟往常的線段樹修改沒什麼區別

void change(int k,int l,int r,int pos,int val)//將pos位置的值改為val

查詢前驅也沒什麼特別的,在詢問區間範圍內就呼叫平衡樹,最後取左右子樹查詢的\(maxn\)

也可以像操作\(1\)那樣分離

//不再注釋

int querypre(int k,int l,int r,int ql,int qr,int val)//查詢[ql,qr]區間內val的前驅

同上,後繼取左右子樹查詢的\(minn\)

int querynxt(int k,int l,int r,int ql,int qr,int val)

\(ps:\)樹套樹的時間複雜度都很優秀 ,所以需要注意一下卡常和各種玄學優化,(但這道題我加快讀比不加慢?!)

**:

#includeusing namespace std;

const int inf=0x7fffffff,n=5e4+10;

int n,m;

int p[n];

int cnt=0;

struct tree

t[n*40];

int seed=233;

struct fhqtreap

int newnode(int val)

void up(int k)

void split(int now,int val,int &a,int &b)

if(t[now].val<=val)

else

up(now);

} int merge(int a,int b)

}return t[now].val;

} int pre(int rt,int val)

int nxt(int rt,int val)

}a[n<<2];

void build(int k,int l,int r)

int queryrank(int k,int l,int r,int ql,int qr,int val)

int queryval(int ql,int qr,int val)

return ans;

}void change(int k,int l,int r,int pos,int val)

int querypre(int k,int l,int r,int ql,int qr,int val)

int querynxt(int k,int l,int r,int ql,int qr,int val)

inline int read()

while(isdigit(ch))

return x*f;

}int main()

if(op==2)

if(op==3)

if(op==4)

if(op==5)

}return 0;}/*

9 64 2 2 1 9 4 0 1 1

2 1 4 3

3 4 10

2 1 4 3

1 2 5 9

4 3 9 5

5 2 8 5

*/

bzoj 3196 樹套樹模板

然而我還是在繼續刷水題。終於解開了區間第k大的心結。比較裸的線段樹套平衡樹,比較不好想的是求區間第k大時需要二分一下答案,然後問題就轉化為了第乙個操作。複雜度nlog3n。跑的比較慢。在查前驅後繼的時候寫錯了。如果要直接賦值ans的話前驅是k x z,後繼是k x 1 include2 includ...

樹套樹專題

對資料結構的不熟練 題目鏈結 嘗試一下樹狀陣列套主席樹的寫法。小細節沒有重視 為了方便起見,一般我們寫getrank x 求的都是 1 include2 const int maxn 50035 3 const int maxnode 5000035 4 const int inf 21474836...

P3380 模板 二逼平衡樹(樹套樹)

若opt 1 則為操作1,之後有三個數l,r,k 表示查詢k在區間 l,r 的排名 若opt 2 則為操作2,之後有三個數l,r,k 表示查詢區間 l,r 內排名為k的數 若opt 3 則為操作3,之後有兩個數pos,k 表示將pos位置的數修改為k 若opt 4 則為操作4,之後有三個數l,r,k...