「学习笔记」fhq-treap

fhq-treap

写splay最容易写挂的两个地方,一处是旋转,另一处是维护child/fa的时候失误导致RE/TLE。fhq-treap这两个问题都没有,因为它是一种无旋treap,并且不需要维护fa指针,也不需要频繁地对child是否为空进行检验,更不需要建"哨兵"结点了。另外fhq-treap能可持久化!

形态

每个点有一个随机分配的权值,记做rnd。我们要时刻保证该平衡树是以rnd为关键字的一个堆。这个权值决定了复杂度的正确性,利用了堆高的期望。

合并 merge

合并时若一方是空结点就直接返回,否则比较\(u,v\)的随机权值,把较小的做根,然后另一个递归合并。注意这是BST的合并,merge的顺序至关重要!

// u和v中序遍历拼在一起
int merge(int u, int v) {
   if(!u || !v) return u | v;
   if(rnd[u] < rnd[v]) {
      ch[u][1] = merge(ch[u][1], v);
      return upd(u), u;
   }
   ch[v][0] = merge(u, ch[v][0]);
   return upd(v), v;
}

分裂 split

按权值分裂:把当前treap分成权值\(\leq v\)\(>v\)的两个BST。

同样递归分裂,若当前点\(u\)的权值\(\leq v\),令\(l=u\),然后递归到右子树,把右子树中\(\leq v\)的接到\(l\)的右儿子上,\(r\)直接递归到右子树求。反之同理。

// 裂成 <= val 和 > val 两部分
void split(int u, int val, int &l, int &r) { 
   if(!u) { l = r = 0; return ; }
   if(w[u] <= val) {
      l = u; split(ch[u][1], val, ch[u][1], r); upd(l);
   } else {
      r = u; split(ch[u][0], val, l, ch[u][0]); upd(r);
   }
}

按大小分类:把当前treap分成中序遍历前\(k\)的点和剩余部分两个BST。

把中序遍历的位置当做权值,和上面是一样的。

// 裂成前k项和剩余两部分
void split(int u, int k, int &l, int &r) {
   if(!u) { l = r = 0; return ; }
   if(sz[ch[u][0]] + 1 <= k) {
      l = u; split(ch[u][1], k - sz[ch[u][0]] - 1, ch[u][1], r); upd(l);
   } else {
      r = u; split(ch[u][0], k, l, ch[u][0]); upd(r);
   }
}

其他操作

  • 插入:按\(v\)分裂,然后合并。

  • 删除:裂成小于\(v\),等于\(v\),大于\(v\)三部分,然后把中间部分合并根的左右儿子,最后把这三部分合并。

  • 第k大:同splay,在treap上走,根据size判断。

  • 排名:把\(\leq v-1\)的裂出来,返回该子树大小 + 1

  • 前驱:把\(\leq v - 1\)的裂出来,返回该子树最后一个元素(用kth)

  • 后继:把\(>v\)的裂出来,返回该子树第一个元素(用kth)

复杂度证明

留坑

普通平衡树

坑点:注意sz[u] = 1这个初始化。

代码:

// P3369 【模板】普通平衡树
#include <algorithm>
#include <cstdlib>
#include <cstdio>
#include <ctime>
using namespace std;
const int N = 1e5 + 10;
struct fhq_treap {

int rt, id, rnd[N], w[N], ch[N][2], sz[N];
void init() { srand(time(0)); rt = id = 0; }
void upd(int u) {
   sz[u] = sz[ch[u][0]] + sz[ch[u][1]] + 1;
}
int newnode(int val) {
   id ++; ch[id][0] = ch[id][1] = 0;
   w[id] = val; rnd[id] = rand(); sz[id] = 1;
   return id;
}
int merge(int u, int v) {
   if(!u || !v) return u | v;
   if(rnd[u] < rnd[v]) {
      ch[u][1] = merge(ch[u][1], v);
      return upd(u), u;
   }
   ch[v][0] = merge(u, ch[v][0]);
   return upd(v), v;
}
void split(int u, int val, int &l, int &r) {
   if(!u) { l = r = 0; return ; }
   if(w[u] <= val) {
      l = u; split(ch[u][1], val, ch[u][1], r); upd(l);
   } else {
      r = u; split(ch[u][0], val, l, ch[u][0]); upd(r);
   }
}
void insert(int val) {
   int l, r; split(rt, val, l, r);
   rt = merge(merge(l, newnode(val)), r);
}
void remove(int val) {
   int l, l2, r;
   split(rt, val, l, r);
   split(l, val - 1, l, l2);
   rt = merge(merge(l, merge(ch[l2][0], ch[l2][1])), r);
}
int rnk(int val) {
   int l, r; split(rt, val - 1, l, r);
   int res = sz[l] + 1; rt = merge(l, r);
   return res;
}
int kth(int k, int st = 0) {
   int u = st ? st : rt;
   while(1) {
      if(k <= sz[ch[u][0]]) u = ch[u][0];
      else {
         k -= sz[ch[u][0]] + 1;
         if(k <= 0) break ;
         u = ch[u][1];
      }
   }
   return w[u];
}
int pre(int val) {
   int l, r; split(rt, val - 1, l, r);
   int res = kth(sz[l], l); rt = merge(l, r);
   return res;
}
int nxt(int val) {
   int l, r; split(rt, val, l, r);
   int res = kth(1, r); rt = merge(l, r);
   return res;
}

} t;
int main() {
   t.init();
   int test; scanf("%d", &test);
   for(int op, x; test --; ) {
      scanf("%d%d", &op, &x);
      if(op == 1) t.insert(x);
      if(op == 2) t.remove(x);
      if(op == 3) printf("%d\n", t.rnk(x));
      if(op == 4) printf("%d\n", t.kth(x));
      if(op == 5) printf("%d\n", t.pre(x));
      if(op == 6) printf("%d\n", t.nxt(x));
   }
   return 0;
}

可持久化平衡树

我们只需要改动split,像主席树那样,把更改结构的点重建出来。由于树高期望\(\log n\),空间复杂度变为\(O(n \log n)\)。实际数组开\(50\)倍稳一点。

void split(int u, int val, int &l, int &r) {
   if(!u) { l = r = 0; return ; }
   if(w[u] <= val) {
      l = clone(u); split(ch[u][1], val, ch[l][1], r); upd(l);
   } else {
      r = clone(u); split(ch[u][0], val, l, ch[r][0]); upd(r);
   }
}

由于split-merge的对称性,merge不需要改变写法。(如果题目有merge操作,还是需要可持久化的)询问操作稍有简化:询问完不需要merge,因为split没有改变原来版本的treap。

代码:

// P3835 【模板】可持久化平衡树
#include <algorithm>
#include <climits>
#include <cstdlib>
#include <cstdio>
#include <ctime>
using namespace std;
const int M = 5e5 + 10, N = 50 * M, INF = INT_MAX;
struct fhq_treap {

int rt[M], id, ver, rnd[N], w[N], ch[N][2], sz[N];
void init() { srand(time(0)); ver = id = rt[0] = 0; }
void upd(int u) {
   sz[u] = sz[ch[u][0]] + sz[ch[u][1]] + 1;
}
int newnode(int val) {
   id ++; ch[id][0] = ch[id][1] = 0;
   w[id] = val; rnd[id] = rand(); sz[id] = 1;
   return id;
}
int clone(int u) {
   id ++; ch[id][0] = ch[u][0]; ch[id][1] = ch[u][1];
   w[id] = w[u]; rnd[id] = rnd[u];
   return id;
}
int merge(int u, int v) {
   if(!u || !v) return u | v;
   if(rnd[u] < rnd[v]) {
      ch[u][1] = merge(ch[u][1], v);
      return upd(u), u;
   }
   ch[v][0] = merge(u, ch[v][0]);
   return upd(v), v;
}
void split(int u, int val, int &l, int &r) {
   if(!u) { l = r = 0; return ; }
   if(w[u] <= val) {
      l = clone(u); split(ch[u][1], val, ch[l][1], r); upd(l);
   } else {
      r = clone(u); split(ch[u][0], val, l, ch[r][0]); upd(r);
   }
}
void insert(int v, int val) {
   int l, r; split(rt[v], val, l, r);
   rt[++ ver] = merge(merge(l, newnode(val)), r);
}
void remove(int v, int val) {
   int l, l2, r;
   split(rt[v], val, l, r);
   split(l, val - 1, l, l2);
   rt[++ ver] = merge(merge(l, merge(ch[l2][0], ch[l2][1])), r);
}
int rnk(int v, int val) {
   int l, r; split(rt[v], val - 1, l, r);
   rt[++ ver] = rt[v];
   return sz[l] + 1;
}
int kth(int v, int k, int st = 0) {
   int u = st ? st : rt[v];
   while(1) {
      if(k <= sz[ch[u][0]]) u = ch[u][0];
      else {
         k -= sz[ch[u][0]] + 1;
         if(k <= 0) break ;
         u = ch[u][1];
      }
   }
   if(!st) rt[++ ver] = rt[v];
   return w[u];
}
int pre(int v, int val) {
   int l, r; split(rt[v], val - 1, l, r);
   int res = l ? kth(v, sz[l], l) : - INF;
   rt[++ ver] = rt[v];
   return res;
}
int nxt(int v, int val) {
   int l, r; split(rt[v], val, l, r);
   int res = r ? kth(v, 1, r) : INF;
   rt[++ ver] = rt[v];
   return res;
}

} t;
int main() {
   t.init();
   int test; scanf("%d", &test);
   for(int v, op, x; test --; ) {
      scanf("%d%d%d", &v, &op, &x);
      if(op == 1) t.insert(v, x);
      if(op == 2) t.remove(v, x);
      if(op == 3) printf("%d\n", t.rnk(v, x));
      if(op == 4) printf("%d\n", t.kth(v, x));
      if(op == 5) printf("%d\n", t.pre(v, x));
      if(op == 6) printf("%d\n", t.nxt(v, x));
   }
   return 0;
}

文艺平衡树

像splay那样打翻转标记即可,一个结点的rev\(1\)表示这个点的子树按中序遍历翻转了。

注意在merge, split和dfs的时候都要下传标记。

// P3391 【模板】文艺平衡树
#include <algorithm>
#include <cstdlib>
#include <cstdio>
#include <ctime>
using namespace std;
const int N = 1e5 + 10;
struct fhq_treap {

int rt, id, rnd[N], ch[N][2], sz[N];
bool rev[N];
void init() { srand(time(0)); rt = id = 0; }
void upd(int u) {
   sz[u] = sz[ch[u][0]] + sz[ch[u][1]] + 1;
}
int newnode() {
   id ++; ch[id][0] = ch[id][1] = rev[id] = 0;
   rnd[id] = rand(); sz[id] = 1;
   return id;
}
int merge(int u, int v) {
   if(!u || !v) return u | v;
   pdown(u); pdown(v);
   if(rnd[u] < rnd[v]) {
      ch[u][1] = merge(ch[u][1], v);
      return upd(u), u;
   }
   ch[v][0] = merge(u, ch[v][0]);
   return upd(v), v;
}
void pdown(int u) {
   if(rev[u]) {
      rev[ch[u][0]] ^= 1; rev[ch[u][1]] ^= 1;
      swap(ch[u][0], ch[u][1]); rev[u] = 0;
   }
}
void split(int u, int k, int &l, int &r) {
   if(!u) { l = r = 0; return ; }
   pdown(u);
   if(sz[ch[u][0]] + 1 <= k) {
      l = u; split(ch[u][1], k - sz[ch[u][0]] - 1, ch[u][1], r); upd(l);
   } else {
      r = u; split(ch[u][0], k, l, ch[u][0]); upd(r);
   }
}
void pushback() {
   rt = merge(rt, newnode());
}
void reverse(int l, int r) {
   int x, y, z;
   split(rt, r, x, z);
   split(x, l - 1, x, y);
   rev[y] ^= 1;
   rt = merge(x, merge(y, z));
}
void dfs(int u) {
   if(u) {
      pdown(u);
      dfs(ch[u][0]);
      printf("%d ", u);
      dfs(ch[u][1]);
   }
}

} t;
int main() {
   t.init();
   int n, test; scanf("%d%d", &n, &test);
   for(int i = 1; i <= n; i ++) t.pushback();
   for(int l, r; test --; ) {
      scanf("%d%d", &l, &r);
      t.reverse(l, r);
   }
   t.dfs(t.rt); putchar('\n');
   return 0;
}

可持久化文艺平衡树

可持久化以后标记没法下放了!懒得写标记永久化,就每次下发标记的时候左右儿子重新建,这样空间常数乘\(2\)

// 下放翻转标记
void pdown(int u) {
   if(rev[u]) {
      if(ch[u][0]) ch[u][0] = clone(ch[u][0]), rev[ch[u][0]] ^= 1;
      if(ch[u][1]) ch[u][1] = clone(ch[u][1]), rev[ch[u][1]] ^= 1;
      swap(ch[u][0], ch[u][1]);
      rev[u] = 0;
   }
}

代码:

// P5055 【模板】可持久化文艺平衡树
#include <algorithm>
#include <cstdlib>
#include <cstdio>
#include <ctime>
using namespace std;
typedef long long ll;
const int M = 2e5 + 10, N = 100 * M;
struct fhq_treap {

int rt[M], id, ver, w[N], rnd[N], ch[N][2], sz[N];
bool rev[N];
ll s[N];
void init() { srand(time(0)); rt[0] = id = ver = 0; }
void upd(int u) {
   sz[u] = sz[ch[u][0]] + sz[ch[u][1]] + 1;
   s[u] = s[ch[u][0]] + s[ch[u][1]] + w[u];
}
int newnode(int val) {
   id ++; ch[id][0] = ch[id][1] = 0;
   w[id] = s[id] = val; rnd[id] = rand(); sz[id] = 1;
   return id;
}
int clone(int u) {
   id ++; ch[id][0] = ch[u][0]; ch[id][1] = ch[u][1];
   w[id] = w[u]; s[id] = s[u]; rnd[id] = rnd[u]; rev[id] = rev[u]; sz[id] = sz[u];
   return id;
}
void pdown(int u) {
   if(rev[u]) {
      if(ch[u][0]) ch[u][0] = clone(ch[u][0]), rev[ch[u][0]] ^= 1;
      if(ch[u][1]) ch[u][1] = clone(ch[u][1]), rev[ch[u][1]] ^= 1;
      swap(ch[u][0], ch[u][1]);
      rev[u] = 0;
   }
}
int merge(int u, int v) {
   if(!u || !v) return u | v;
   if(rnd[u] < rnd[v]) {
      pdown(u);
      ch[u][1] = merge(ch[u][1], v);
      return upd(u), u;
   }
   pdown(v);
   ch[v][0] = merge(u, ch[v][0]);
   return upd(v), v;
}
void split(int u, int k, int &l, int &r) {
   if(!u) { l = r = 0; return ; }
   pdown(u);
   if(sz[ch[u][0]] + 1 <= k) {
      l = clone(u); split(ch[u][1], k - sz[ch[u][0]] - 1, ch[l][1], r); upd(l);
   } else {
      r = clone(u); split(ch[u][0], k, l, ch[r][0]); upd(r);
   }
}
void insert(int v, int k, int x) {
   int l, r; split(rt[v], k, l, r);
   rt[++ ver] = merge(l, merge(newnode(x), r));
}
void remove(int v, int k) {
   int l, l2, r;
   split(rt[v], k, l, r);
   split(l, k - 1, l, l2);
   rt[++ ver] = merge(l, r);
}
void reverse(int v, int l, int r) {
   int x, y, z;
   split(rt[v], r, x, z);
   split(x, l - 1, x, y);
   rev[y] ^= 1;
   rt[++ ver] = merge(x, merge(y, z));
}
ll query(int v, int l, int r) {
   int x, y, z;
   split(rt[v], r, x, z);
   split(x, l - 1, x, y);
   ll res = s[y];
   rt[++ ver] = rt[v];
   return res;
}

} t;
int main() {
   t.init();
   int test; scanf("%d", &test);
   ll p, x, la = 0;
   for(int v, op; test --; ) {
      scanf("%d%d", &v, &op);
      if(op == 1) { scanf("%lld%lld", &p, &x); t.insert(v, p ^ la, x ^ la); }
      if(op == 2) { scanf("%lld", &p); t.remove(v, p ^ la); }
      if(op == 3) { scanf("%lld%lld", &p, &x); t.reverse(v, p ^ la, x ^ la); }
      if(op == 4) { scanf("%lld%lld", &p, &x); printf("%lld\n", la = t.query(v, p ^ la, x ^ la)); }
   }
   return 0;
}
posted @ 2020-06-04 21:07  hfhongzy  阅读(230)  评论(0编辑  收藏  举报