淺談 fhq-treap(無旋treap)
淺談 fhq-treap(無旋treap)
(emm剛寫了這個,就放這個吧)
\(fhq-treap\) 也叫做 無旋treap,由防火牆範浩強大佬發明。
個人認為是平衡樹中碼量最少,也最容易理解的一種寫法。
主要思想
顧名思義,無旋意味著它沒有旋轉操作(終於沒有噁心人的旋轉了)。
事實上,無旋treap只有兩種操作。
一種是 \(split\)(分裂),另一種是 \(merge\)(合併)。
平衡樹中的各種操作都可以用這兩個函式來搞定。下面我們來一一分析。
常見操作
-
插入一個整數 \(x\)
-
刪除一個整數 \(x\)(若有多個相同的數,只刪除一個)。
-
查詢整數 \(x\) 的排名(排名定義為比當前數小的數的個數 +1)。
-
查詢排名為 \(x\) 的數(如果不存在,則認為是排名小於 \(x\) 的最大數。保證 \(x\) 不會超過當前資料結構中數的總數)。
-
求 \(x\) 的前驅(前驅定義為小於 \(x\),且最大的數)。
-
求 \(x\) 的後繼(後繼定義為大於 \(x\),且最小的數)。
當然區間反轉也是可以用無旋\(treap\) 實現的,這裡先不講。
函式解釋
split(分裂)
\(split\) 函式要實現把一棵平衡樹拆成兩棵,左邊的樹上節點的值都小於等於 \(k\), 右邊的樹上節點的值都大於等於 \(k\)
-
\(x:\) 當前子樹根節點
-
\(k:\) 以 \(k\) 為分界線分割樹
-
\(a, b:\) 分裂之後兩棵子樹的根,左邊的子樹根是 \(a\),右邊的根是 \(b\),這裡要傳地址,方便寫。
注意: 當根為 0 時,一定一定一定要清空 \(a\),&b&,不然會死迴圈(我就在這裡卡了好久QWQ)
inline void split(int x, int k, int &a, int &b){ if(!x){ a = b = 0; //清空清空清空(重要的事情說三遍) return; } if(t[x].val <= k){ a = x; //左邊子樹已經分裂出去了,直接a=x split(rs(x), k, rs(x), b); //在右子樹上查詢,把拆下來右子樹上節點值<=k的點放回到x的右子樹上 }else{ b = x; //同理 split(ls(x), k, a, ls(x)); } pushup(x); }
merge(合併)
\(merge\) 函式要實現把兩個分開的樹再合併到一起去,並返回合併後的根節點。
- \(x, y:\) 兩棵樹的根
這個操作類似於線段樹合併。
注意:合併時只能是兩個相鄰的子樹合併,不能跳著合併。
例如:樹 \(rt\) ---> 子樹\(a\) 和 子樹\(b\)
子樹\(b\) ---> 子樹\(c\) 和 子樹\(d\) (---> 表示拆成兩棵樹)
那麼我們合併時,只能 \(merge(a, merge(c,d))\) 或 \(merge(merge(a, c), d)\)。
而不能 \(merge(merge(a, d), c)\)
inline int merge(int x, int y){
if(!x || !y) return x + y;
if(t[x].wei <= t[y].wei){ //wei是隨機值,wei(x) <= wei(y) 時,讓 y 作為 x 的子樹
rs(x) = merge(rs(x), y);
pushup(x);
return x;
}else{ //反之,x 作為 y 的子樹
ls(y) = merge(x, ls(y));
pushup(y);
return y;
}
}
insert(插入)
先新建一個點設為 \(y\),把原樹拆成 \(x\) 和 \(z\)。
\(x\) 中節點值 \(<= k\),\(z\) 中節點值 \(>k\)。
然後一次合併 \(x\),\(y\),\(z\)即可。
inline void insert(int k){
t[++tot].val = k, t[tot].wei = rand();
t[tot].siz = 1;
split(root, k, a, b);
root = merge(merge(a, tot), b);
}
remove(刪除)
這個就很巧妙了,我們把值 \(<=k\) 的樹先拆出來設為 \(a\),然後把值 \(<k\) 的點再拆出來,剩餘的部分設為為 \(c\)。
那麼現在 \(c\) 中的點就是 \(=k\) 的。
我們直接合並 \(c\) 的左右兩棵子樹,這樣就相當於把根節點刪了。
最後合併回去。
inline void remove(int k){
split(root, k, a, b); //拆,<=k的存到a裡,>k的存到b裡。
split(a, k - 1, a, c); //再拆,<k的存到a裡,=k的存到裡。
c = merge(ls(c), rs(c)); //合併c左右子樹
root = merge(merge(a, c), b); //再把a,b合併上去。
}
check_rk
查詢 \(k\) 的排名。
我們把 \(<k\) 的的點從樹上分裂出來,然後分出來的樹的大小 +1 就是排名。
別忘了合併回去。
inline int check_rk(int k){
int rank;
split(root, k - 1, a, b);
rank = t[a].siz + 1;
root = merge(a, b);
return rank;
}
check_val
查詢排名為 \(k\) 的數。
我這裡用的遞迴寫法。
-
\(x\) 當前子樹的根。
-
\(k\) 在當前子樹中排名多少。
我這裡用的遞迴寫法。(程式碼應該挺好理解的吧)
inline int check_val(int x, int k){
if(k == t[ls(x)].siz + 1) return t[x].val;
if(k <= t[ls(x)].siz) return check_val(ls(x), k); //在左子樹中,直接跑左子樹裡查
else return check_val(rs(x), k - t[ls(x)].siz - 1); //在右子樹中,減去左子樹大小,再減1(根),就是在右子樹中的排名。
}
check_pre
查詢前驅。
這個也好說,我們先查排名,設排名為 \(rk\),我們再查排名為 \(rk - 1\) 的數是多少,就是前驅了。
inline int check_pre(int x){
return check_val(root, check_rk(x) - 1);
}
check_next
查詢 \(x\) 的後繼,基本同理,但略有不同。
因為 \(x\) 可能有多個,我們查 \(x\) 的排名後 +1,不一定是 \(x + 1\),有可能還是 \(x\)。
那我們怎麼辦呢?
其實也不難,我們直接查 \(x + 1\) 的排名,沒有?沒關係,就算沒有 \(x + 1\),查出來的也是大於 \(x\) 的最小的數的排名。
然後我們再查一下這個排名的值即可。
inline int check_next(int x){
return check_val(root, check_rk(x + 1));
}
然後……就沒有然後了吧。
主函式我就不用多說了吧。
完整程式碼
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#define ls(x) t[x].ch[0]
#define rs(x) t[x].ch[1]
using namespace std;
const int N = 2e6; //原本的數+運算元。最多有1.1e6個數
struct Tree{
int wei, val, siz, ch[2];
}t[N];
int n, m, tot, root;
int last, ans, a, b, c;
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * f;
}
inline void pushup(int x){
t[x].siz = t[ls(x)].siz + t[rs(x)].siz + 1;
}
inline void split(int x, int k, int &a, int &b){
if(!x){
a = b = 0;
return;
}
if(t[x].val <= k){
a = x;
split(rs(x), k, rs(x), b);
}else{
b = x;
split(ls(x), k, a, ls(x));
}
pushup(x);
}
inline int merge(int x, int y){
if(!x || !y) return x + y;
if(t[x].wei <= t[y].wei){
rs(x) = merge(rs(x), y);
pushup(x);
return x;
}else{
ls(y) = merge(x, ls(y));
pushup(y);
return y;
}
}
inline void insert(int k){
t[++tot].val = k, t[tot].wei = rand();
t[tot].siz = 1;
split(root, k, a, b);
root = merge(merge(a, tot), b);
}
inline void remove(int k){
split(root, k, a, b);
split(a, k - 1, a, c);
c = merge(ls(c), rs(c));
root = merge(merge(a, c), b);
}
inline int check_rk(int k){
int rank;
split(root, k - 1, a, b);
rank = t[a].siz + 1;
root = merge(a, b);
return rank;
}
inline int check_val(int x, int k){
if(k == t[ls(x)].siz + 1) return t[x].val;
if(k <= t[ls(x)].siz) return check_val(ls(x), k);
else return check_val(rs(x), k - t[ls(x)].siz - 1);
}
inline int check_pre(int x){
return check_val(root, check_rk(x) - 1);
}
inline int check_next(int x){
return check_val(root, check_rk(x + 1));
}
int main(){
n = read(), m = read();
for(int i = 1; i <= n; i++){
int x;
x = read();
insert(x);
}
while(m--){
int op, x;
op = read(), x = read() ^ last;
if(op == 1) insert(x);
if(op == 2) remove(x);
if(op <= 2) continue;
if(op == 3) last = check_rk(x);
if(op == 4) last = check_val(root, x);
if(op == 5) last = check_pre(x);
if(op == 6) last = check_next(x);
ans ^= last;
}
printf("%d\n", ans);
return 0;
}
End
本文來自部落格園,作者:{xixike},轉載請註明原文連結:https://www.cnblogs.com/xixike/p/15132464.html