static_cast、dynamic_cast、reinterpret_cast、const_cast以及C強制型別轉換的區別 static_cast、dynamic_cast、reinterpret_cast、const_cast以及C強制型別轉換的區別
平衡樹
滿足等式:
\[\Huge{Treap=Tree+heap} \]因此 \(\text{Treap}\) 樹堆其實就是樹+堆。樹是二叉查詢樹 \(\text{BST}\),堆是二叉堆,大根堆小根堆都可以。
二叉查詢樹BST
就是一棵二叉樹,滿足一個條件:左子樹所有節點<父節點<右子樹所有節點
因此,二叉樹的中序遍歷顯然就是一個單調遞增或遞減的數列
同時,二叉查詢樹還有一些很好的性質,方便我們尋找一個數的位置,期望複雜度是 \(O(log n)\)
如:
插入一個數,刪去一個數,查詢一個數的rk,找一個數的前驅和後繼
這就是一個合法的 \(BST\)
heap
heap就是堆,可以是大根堆也可以是小根堆,看自己的需要
Treap
對於 \(\text{Treap}\) 來說,我們用二叉查詢樹的搜尋順序,但是我們每個節點的值讓它都滿足的堆的性質
我們一般為了保證 \(\text{Treap}\) 的平衡性,不得不對於每個點隨機賦值
每次都隨機一個權值作為它的附加權值,那麼它就大概率是平衡的。
fhq Treap(非旋treap)
一般的平衡樹都會有很惱人的旋轉,又不好寫,又不好調,但是fhq大佬解決了這個問題。
fhq-treap(又稱xixike-treap)
注意:這裡的 \(val\) 滿足 \(\text{BST}\) \(rnd\) 滿足堆的性質
對於上面兩個圖來說,\(val_1<val_2\)
核心思想有兩個:split 和 merge,表示裂開和合並
\(\text{Google}\) 翻譯:split 分離,分裂
它的主要思想就是把一個Treap分成兩個。
split操作有兩種型別,一種是按照權值分配,一種是按前k個分配。
第一種就是把所有小於k的權值的節點分到一棵樹中,第二種是把前k個分到一個樹裡。
我們提前宣告一個 \(\mathrm{update}\) 函式來更新某個點的 \(siz\) 大小
權值版:
inline void split(int p, int k, int& rt1, int& rt2) { if (!p) return rt1 = rt2 = 0, void(); if (tr[p].key <= k) rt1 = p, split(tr[p].r, k, tr[p].r, rt2); else rt2 = p, split(tr[p].l, k, rt1, tr[p].l); update(p); }
這樣寫的話我們最後的 \(rt_1\) 就是小於等於 \(k\) 的數的子樹的根,同理 \(rt_2\) 就是大於 \(k\) 的數的子樹的根
對於我們遍歷到每一個點,假如它的權值小於 \(k\),那麼它的所有左子樹,都要分到左邊的樹裡,然後遍歷它的右兒子。假如大於 \(k\),把它的所有右子樹分到右邊的樹裡,遍歷左兒子。
因為它的最多操作次數就是一直分到底,效率就是 \(O(log ~ n)\)
對於前 \(k\) 個版的,就是像找第 \(k\) 大的感覺。每次減掉 siz
inline void split(int p, int k, int& rt1, int& rt2) {
if (!p) return rt1 = rt2 = 0, void();
if (tr[p].siz <= k) rt1 = p, split(tr[p].r, k, tr[p].r, rt2);
else rt2 = p, split(tr[p].l, k - tr[p].siz - 1, rt1, tr[p].l);
update(p);
}
這就是我們的 \(\mathrm {split}\) 操作了,它可以把一棵樹分成左子樹和右子樹
然後就是 \(\mathrm{merge}\) 操作,這個的話我們還是需要一些特殊的方式合併,由於我們合併時無法統一樹高,所以我們核心是以 \(rand\) 值進行 \(\mathrm{merge}\) 的,合併過程中一定要始終保持 \(rand\) 值大的在前面另一個直接作為前一個的兒子和其原來的兒子進行合併即可。
我們寫 \(\mathrm{merge}\) 之前一定要牢記一點,子樹 \(rt_1\) 的 \(key\) 一定要小於子樹 \(rt_2\) 的 \(key\) 的時候你才可以 \(\mathrm{merge}\)
inline int merge(int rt1, int rt2){
if (!rt1 || !rt2) return rt1 + rt2;
if (tr[rt1].val > tr[rt2].val) {
tr[rt1].r = merge(tr[rt1].r, rt2);
update(rt1);
return rt1;
}
else {
tr[rt2].l = merge(rt1, tr[rt1].l);
update(rt2);
return rt2;
}
}
返回的每個值都是根
以上兩個操作,就是 \(\mathrm{PHQ-treap}\) 的精髓了,其餘所有操作都是基於這兩個操作展開的
首先是插入一個數 \(\mathrm{insert}\) 插入一個權值為 \(k\) 的點,可以先讓原樹裂開,然後把它和權值小於等於 \(k\) 的那棵樹 \(\mathrm{merge}\),然後再用 \(\mathrm{merge}\) 後的那棵樹進行二次 \(\mathrm{merge}\),那麼程式碼應該長成這樣
inline void insert(int k) {
split(root, k, x, y);
root = merge(merge(x, new_node(k)), y);
}
補充一下 new_node
函式,這個函式是說我們新開一個點,那麼應該很容易:
struct node {
int key, val, siz;
int l, r;
}tr[N << 1];
inline void new_node(int k) {
tr[++cnt] = (node){k, rnd(), 1, 0, 0}
}
然後是如何刪除一個權值為 \(k\) 的點
首先是把按照權值小於等於 \(k\) 和大於 \(k\) 裂開成 \(x\) 子樹和 \(y\) 子樹,然後再讓 \(x\) 子樹裂開成小於 \(k\) 和等於 \(k\) 的子樹 \(x\) 和 \(z\),然後我們直接讓 \(y\) 的兩個兒子 \(\mathrm{merge}\) 一下,然後再 merge(merge(x, y), z)
,就好了
如果是刪除多個的話直接合並 \(x\) 和 \(z\) 即可
inline void delete(int k) {
split(root, k, x, y);
split(x, k - 1, x, y);
y = merge(tr[y].l, tr[y].r);
root = merge(merge(x, y), z);
}
然後是 get_rank
函式,就是查詢小於等於某個權值的有多少個,直接split一下查一下,然後再merge回去就好
inline void get_rank(int k) {
split(root, k - 1, x, y);
write(tr[x].siz + 1, '\n');
root = merge(x, y);
}
然後是 get_key
函式,查詢某個排名對應的權值,這個有一點麻煩,首先我們還是想著如何通過 split
和 merge
來get到排名對應的權值,一個最簡單的辦法就是,我們迴圈的呼叫子樹的左右兩邊的 siz 即可
inline int get_key(int rank) {
int p = root;
while (p) {
if (tr[tr[p].l].siz + 1 == rank) break;
if (tr[tr[p].l].siz >= rank) p = tr[p].l;
else rank -= tr[tr[p].l].siz, p = tr[p].r;
}
return tr[p].key;
}
然後是查小於 \(k\) 的最大的數,或者稱為前驅,我們先把小於 \(k\) 的一部分先裂開,然後一直往右找到底即可
inline int get_prev(int k) {
split(root, k - 1, x, y);
int p = x;
while (tr[p].r) p = tr[p].r;
int ret = tr[p].key;
root = merge(x, y);
return ret;
}
同理是查大於 \(k\) 的最大的數,或者稱為後驅,我們先把大於 \(k\) 的一部分裂開,然後一路往左邊找
inline int get_next(int k) {
split(root, k - 1, x, y);
int p = y;
while (tr[p].l) p = tr[p].l;
int ret = tr[p].key;
root = merge(x, y);
return ret;
}
這就是最基本的操作們了!
我們也因此可以完成 P3369【模板】普通平衡樹
/*
Blackpink is the Revolution
light up the sky
Blackpink in your area
*/
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cctype>
#include <bitset>
#include <vector>
#include <cstdio>
#include <random>
#include <cmath>
#include <queue>
#include <ctime>
#include <map>
#include <set>
#define rep(i, a, b) for(int i = (a); (i) <= (b); ++i)
#define per(i, a, b) for(int i = (a); (i) >= (b); --i)
#define whlie while
using namespace std;
using ll = long long;
using P = pair<int ,int>;
namespace scan {
template <typename T>
inline void read(T &x) {
x = 0; char c = getchar(); int f = 0;
for (; !isdigit(c); c = getchar()) f |= (c == '-');
for (; isdigit(c); c=getchar()) x = x * 10 + (c ^ 48);
if (f) x = -x;
}
template <typename T, typename ...Args>
inline void read(T &x, Args &...args) {
read(x), read(args...);
}
template <typename T>
inline void write(T x, char ch) {
if (x < 0) putchar('-'), x = -x;
static short st[30], tp;
do st[++tp] = x % 10, x /= 10; while(x);
while (tp) putchar(st[tp--] | 48);
putchar(ch);
}
template <typename T>
inline void write(T x) {
if (x < 0) putchar('-'), x = -x;
static short st[30], tp;
do st[++tp] = x % 10, x /= 10; while(x);
while(tp) putchar(st[tp--] | 48);
}
inline void write(char ch){
putchar(ch);
}
template <typename T, typename ...Args>
inline void write(T x, char ch, Args ...args) {
write(x, ch), write(args...);
}
} //namespace scan
using namespace scan;
mt19937 rnd(114514);
const int mod = 1e9 + 7;
const int N = 4e5 + 5;
int n, m, T, ans, op, root, x, y, z, cnt;
namespace RevolutionBP {
struct node {
int key, val, siz, l, r;
}tr[N << 2];
inline void update(int p) {tr[p].siz = tr[tr[p].l].siz + tr[tr[p].r].siz + 1;}
inline int new_node(int key) {tr[++cnt] = (node){key, rnd(), 1, 0, 0}; return cnt;}
inline void split(int p, int k, int& x, int& y) {
if (!p) return x = y = 0, void();
if (tr[p].key <= k) x = p, split(tr[p].r, k, tr[p].r, y);
else y = p, split(tr[p].l, k, x, tr[p].l);
update(p);
}
inline int merge(int x, int y) {
if (!x || !y) return x + y;
if (tr[x].val <= tr[y].val) return tr[y].l = merge(x, tr[y].l), update(y), y;
else return tr[x].r = merge(tr[x].r, y), update(x), x;
}
inline void insert(int key) {
split(root, key, x, y);
root = merge(merge(x, new_node(key)), y);
}
inline void Delete(int key) {
split(root, key, x, z);
split(x, key - 1, x, y);
y = merge(tr[y].l, tr[y].r);
root = merge(merge(x, y), z);
}
inline void get_rank(int k) {
split(root, k - 1, x, y);
write(tr[x].siz + 1, '\n');
root = merge(x, y);
}
inline int get_key(int rank) {
int p = root;
while (p) {
if (tr[tr[p].l].siz + 1 == rank) break;
if (tr[tr[p].l].siz < rank) rank -= tr[tr[p].l].siz + 1, p = tr[p].r;
else p = tr[p].l;
}
return tr[p].key;
}
inline int get_prev(int k) {
split(root, k - 1, x, y);
int p = x;
while (tr[p].r) p = tr[p].r;
int ret = tr[p].key;
root = merge(x, y);
return ret;
}
inline int get_next(int k) {
split(root, k , x, y);
int p = y;
while (tr[p].l) p = tr[p].l;
int ret = tr[p].key;
root = merge(x, y);
return ret;
}
void main() {
read(T);
while (T--) {
read(op, m);
if (op == 1) insert(m);
if (op == 2) Delete(m);
if (op == 3) get_rank(m);
if (op == 4) write(get_key(m), '\n');
if (op == 5) write(get_prev(m), '\n');
if (op == 6) write(get_next(m), '\n');
}
return void();
}
/*
插入 x 數
刪除 x 數(若有多個相同的數,因只刪除一個)
查詢 x 數的排名(排名定義為比當前數小的數的個數 +1+1 )
查詢排名為 x 的數
求 x 的前驅(前驅定義為小於 x,且最大的數)
求 x 的後繼(後繼定義為大於 x,且最小的數)
*/
}
signed main(){
RevolutionBP::main();
return 0;
}
//write: RevolutionBP