FHQ-Treap P3369 【模板】普通平衡樹

xxb3.1415發表於2020-09-30

Treap是 二叉搜尋樹(BST)和二叉堆(Heap)的結合。二叉搜尋樹支援Treap的所有一般功能,例如查排名,查第k大,前驅,後繼,刪除,插入。它的特點是左子樹小於等於根,右子樹大於等於根。但是它的複雜度依賴於樹的高度,而樹的高度很容易被資料卡成鏈。

Heap是一種完全二叉樹,它的樹高為log(n)。一般有小根堆和大根堆。我們以小根堆為例,它的特點是根節點小於等於子節點。

其實到這裡我們會發現兩種樹的性質是矛盾的,BST要求左兒子小於等於根,右兒子大於等於根,Heap要求根既小於等於左兒子,又小於等於右兒子,同時人為規定左兒子小於右兒子。

所以為了讓二者結合,我們給二叉搜尋樹的每個節點賦予一個隨機值,通過這個隨機值來維護堆的性質。

首先我們要知道,BST的中序遍歷是不變的(最小的在最左邊),而Heap的後序遍歷是不變的(最大的在最右邊)。資料是出題人給的,也就是我們無法改變中序遍歷。現在假設我們的隨機陣列是B,那麼根據這個陣列可以得到唯一的後序遍歷,有了後序遍歷和中序遍歷我們就可以唯一的確定一棵樹。Treap一個重要的特性就是樹的形狀是確定的。在隨機的情況下,樹的高度是log(n)的。證明方法就不展開講了。

Treap是可以通過Zig和Zag來維護的,但是今天講的是利用分裂還有合併來保持平衡,單次操作的期望是log(N)。

宣告一些變數

const int N = 2e6+5;
int val[N], ch[N][2], siz[N], rnd[N], q[N];
int root, cnt, x, y, z, an, tot;

分裂是按照權值來分,(其實也可以按照size來分,一般做區間問題的時侯會用到)。把樹分為兩個子樹,其中左子樹小於等於a,右子樹大於a。

void split(int rt, int a, int &x, int &y) {
  if(!rt) x = y = 0;
  else {
    if(val[rt] <= a) x = rt, split(ch[rt][1], a, ch[rt][1], y);
    else y = rt, split(ch[rt][0], a, x, ch[rt][0]);
    up(rt);
  }
}

合併是按照隨機數數合併的,一定要注意順序,x一定是更小節點的根。然後按照堆的性質來合併即可

int merge(int x, int y) {
  if(!x || !y) return x + y;
  if(rnd[x] < rnd[y]) {
    ch[x][1] = merge(ch[x][1], y);up(x); return x;
  } else {
    ch[y][0] = merge(x, ch[y][0]);up(y); return y;
  } 
} 
void insert(int a) {// 插入
  split(root, a, x, y);
  root = merge(merge(x, new_node(a)), y);
}
void del(int a) { // 刪除,加入了垃圾回收
  split(root, a, x, y);
  split(x, a-1, x, z);
  q[++tot] = z;
  z = merge(ch[z][0], ch[z][1]);
  root = merge(merge(x, z), y);
}
int _rank(int a) {//查詢a的排名
  split(root, a-1, x, y);
  an = siz[x] + 1;
  root = merge(x, y);
  return an;
}
int _kth(int rt, int k) {// 查詢排名為k的數
  while(rt) {
    if(k <= siz[ch[rt][0]]) rt = ch[rt][0];
    else if(k == siz[ch[rt][0]] + 1) return val[rt];
    else k -= siz[ch[rt][0]]+1, rt = ch[rt][1];
  }
}
int pre(int a) {// 查詢前驅
  split(root, a-1, x, y);
  an = _kth(x, siz[x]);
  root = merge(x, y);
  return an;
}
int nxt(int a) { // 查詢後繼
  split(root, a, x, y);
  an = _kth(y, 1);
  root = merge(x, y);
  return an;
}

完整程式碼

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const double Pi = acos(-1);
namespace {
  template <typename T> inline void read(T &x) {
    x = 0; T f = 1;char s = getchar();
    for(; !isdigit(s); s = getchar()) if(s == '-') f = -1;
    for(;  isdigit(s); s = getchar()) x = (x << 3) + (x << 1) + (s ^ 48);
    x *= f;
  }
}
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define _for(n,m,i) for (register int i = (n); i <  (m); ++i)
#define _rep(n,m,i) for (register int i = (n); i <= (m); ++i)
#define _srep(n,m,i)for (register int i = (n); i >= (m); i--)
#define _sfor(n,m,i)for (register int i = (n); i >  (m); i--)
#define lson rt << 1, l, mid
#define rson rt << 1 | 1, mid + 1, r
#define lowbit(x) x & (-x)
#define pii pair<int,int>
#define fi first
#define se second
const int N = 2e6+5;
int val[N], ch[N][2], siz[N], rnd[N], q[N];
int root, cnt, x, y, z, an, tot;
void up(int rt) { 
  siz[rt] = 1 + siz[ch[rt][0]] + siz[ch[rt][1]];
} 
int new_node(int x) {
  int new_cnt = tot ? q[tot--] : ++cnt;
  val[new_cnt] = x;
  siz[new_cnt] = 1;
  rnd[new_cnt] = rand();
  ch[new_cnt][0] = ch[new_cnt][1] = 0;
  // cout << "debug:  " << new_cnt << endl;
  return new_cnt;
}
void split(int rt, int a, int &x, int &y) {
  if(!rt) x = y = 0;
  else {
    if(val[rt] <= a) x = rt, split(ch[rt][1], a, ch[rt][1], y);
    else y = rt, split(ch[rt][0], a, x, ch[rt][0]);
    up(rt);
  }
}
int merge(int x, int y) {
  if(!x || !y) return x + y;
  if(rnd[x] < rnd[y]) {
    ch[x][1] = merge(ch[x][1], y);up(x); return x;
  } else {
    ch[y][0] = merge(x, ch[y][0]);up(y); return y;
  } 
} 
void insert(int a) {
  split(root, a, x, y);
  root = merge(merge(x, new_node(a)), y);
}
void del(int a) {
  split(root, a, x, y);
  split(x, a-1, x, z);
  q[++tot] = z;
  z = merge(ch[z][0], ch[z][1]);
  root = merge(merge(x, z), y);
}
int _rank(int a) {
  split(root, a-1, x, y);
  an = siz[x] + 1;
  root = merge(x, y);
  return an;
}
int _kth(int rt, int k) {
  while(rt) {
    if(k <= siz[ch[rt][0]]) rt = ch[rt][0];
    else if(k == siz[ch[rt][0]] + 1) return val[rt];
    else k -= siz[ch[rt][0]]+1, rt = ch[rt][1];
  }
}
int pre(int a) {
  split(root, a-1, x, y);
  an = _kth(x, siz[x]);
  root = merge(x, y);
  return an;
}
int nxt(int a) {
  split(root, a, x, y);
  an = _kth(y, 1);
  root = merge(x, y);
  return an;
}
int main() { 
  srand(time(0));
  int n,m, op, a, la = 0, ans = 0; read(n); read(m);
  while(n--) {
    read(a); insert(a);
  }
  while(m--) { 
    read(op); read(a);
    a ^= la;
    if(op == 1) insert(a);
    else if(op == 2) del(a);
    else if(op == 3) la = _rank(a),ans ^= la;
    else if(op == 4) la = _kth(root, a), ans ^= la;
    else if(op == 5) la = pre(a), ans ^= la;
    else la = nxt(a), ans ^= la;
  } 
  printf("%d\n", ans);
} 

相關文章