fhq-treap学习笔记
fhq-treap即非旋treap,这里先简单整理一下点权分裂的基本操作。
变量声明
ll ch[N][2], val[N], rnd[N], siz[N], sz;
//ch[x][0/1]:x的左/右儿子
//val[x]:x的值
//rnd[x]:x的附加权值
//siz[x]:以x为根子树的点数
//sz:cnt
分裂
将树分为两棵树,其中树A的最大点权小于等于树B的最小点权。
当访问到一个点时:
1、该点值小于等于k:把左子树划分到x中。
2、该点值大于k:把右子树划分到y中。
边界情况将x,y均设置为0即可,反正回去的时候也会用&赋上值
void split(ll now, ll k, ll &x, ll &y) {//根据点权分裂 if (!now) x = y = 0; else { if (val[now] <= k) x = now, split(ch[now][1], k, ch[now][1], y); else y = now, split(ch[now][0], k, x, ch[now][0]); update(now);//别忘了update } }
void split(ll now, ll k, ll &x, ll &y) {//根据排名分裂 if (!now) x = y = 0; else { if (k <= siz[ch[now][0]]) { y = now; split(ch[now][0], k, x, ch[now][0]); } else { x = now; split(ch[now][1], k - (siz[ch[now][0]] + 1), ch[now][1], y); } update(now);//别忘了update
}
}
合并
保证第一个treap的点值都小于第二个,通过比较rnd确定treap的形态。
若第一个rnd小:保留x的左子树,x的右儿子往下走。
反之:保留y的右子树,y的左儿子往下走。
ll merge(ll x, ll y) { if (!x || !y) return x + y; if (rnd[x] < rnd[y]) { ch[x][1] = merge(ch[x][1], y); update(x);//别忘了update return x; } else { ch[y][0] = merge(x, ch[y][0]); update(y);//别忘了update return y; } }
插入
根据插入的a的值分裂,然后将a合并到x上,再一起合并到y上。
split(root, a, x, y);
root = merge(merge(x, new_node(a)), y);
删除
根据a分裂为x,z,再把x根据a-1分裂为x,y。
此时y的根节点值一定为a,忽略y的根节点即可(合并y的左右儿子)。
最后将x,y,z合并。
split(root, a, x, z); split(x, a - 1, x, y); y = merge(ch[y][0], ch[y][1]); root = merge(merge(x, y), z);
查询a的排名
split(root, a - 1, x, y); printf("%lld\n", siz[x] + 1); root = merge(x, y);
查询排名为a的数
ll kth(ll now, ll k) { while (1) { if (k <= siz[ch[now][0]]) now = ch[now][0]; else if (k == siz[ch[now][0]] + 1) return now; else k -= siz[ch[now][0]] + 1, now = ch[now][1]; } }
printf("%lld\n", val[kth(root, a)]);
前驱/后继
前驱:按a-1分裂,在x里寻找最大值
后继:按a分裂,在y里寻找最小值
//前驱 split(root, a - 1, x, y); printf("%lld\n", val[kth(x, siz[x])]); root = merge(x, y);
//后继 split(root, a, x, y); printf("%lld\n", val[kth(y, 1)]); root = merge(x, y);
完整代码
#define N 500005 ll ch[N][2], val[N], rnd[N], siz[N], sz; void update(ll x) { siz[x] = 1 + siz[ch[x][0]] + siz[ch[x][1]]; } ll new_node(ll v) { siz[++sz] = 1; val[sz] = v; rnd[sz] = rand(); return sz; } void split(ll now, ll k, ll &x, ll &y) { if (!now) x = y = 0; else { if (val[now] <= k) x = now, split(ch[now][1], k, ch[now][1], y); else y = now, split(ch[now][0], k, x, ch[now][0]); update(now); } } /* void split(ll now, ll k, ll &x, ll &y) { if (!now) x = y = 0; else { if (k <= siz[ch[now][0]]) { y = now; split(ch[now][0], k, x, ch[now][0]); } else { x = now; split(ch[now][1], k - (siz[ch[now][0]] + 1), ch[now][1], y); } update(now); } } */ ll merge(ll x, ll y) { if (!x || !y) return x + y; if (rnd[x] < rnd[y]) { ch[x][1] = merge(ch[x][1], y); update(x); return x; } else { ch[y][0] = merge(x, ch[y][0]); update(y); return y; } } ll kth(ll now, ll k) { while (1) { if (k <= siz[ch[now][0]]) now = ch[now][0]; else if (k == siz[ch[now][0]] + 1) return now; else k -= siz[ch[now][0]] + 1, now = ch[now][1]; } } ll T, x, y, z, root; int main() { srand((unsigned)time(NULL)); scanf("%lld", &T); while (T--) { ll opt = read(), a = read(); if (opt == 1) {//插入 split(root, a, x, y); root = merge(merge(x, new_node(a)), y); } else if (opt == 2) {//删除 split(root, a, x, z); split(x, a - 1, x, y); y = merge(ch[y][0], ch[y][1]); root = merge(merge(x, y), z); } else if (opt == 3) {//查询k的排名 split(root, a - 1, x, y); printf("%lld\n", siz[x] + 1); root = merge(x, y); } else if (opt == 4)//查询排名为k的数 printf("%lld\n", val[kth(root, a)]); else if (opt == 5) {//前驱 split(root, a - 1, x, y); printf("%lld\n", val[kth(x, siz[x])]); root = merge(x, y); } else {//后继 split(root, a, x, y); printf("%lld\n", val[kth(y, 1)]); root = merge(x, y); } } return 0; }
#define N 500005 ll ch[N][2], val[N], rnd[N], siz[N], sz; void update(ll x) { siz[x] = 1 + siz[ch[x][0]] + siz[ch[x][1]]; } ll new_node(ll v) { siz[++sz] = 1; val[sz] = v; rnd[sz] = rand(); return sz; } void split(ll now, ll k, ll &x, ll &y) { if (!now) x = y = 0; else { if (val[now] <= k) x = now, split(ch[now][1], k, ch[now][1], y); else y = now, split(ch[now][0], k, x, ch[now][0]); update(now); } } ll merge(ll x, ll y) { if (!x || !y) return x + y; if (rnd[x] < rnd[y]) { ch[x][1] = merge(ch[x][1], y); update(x); return x; } else { ch[y][0] = merge(x, ch[y][0]); update(y); return y; } } ll kth(ll now, ll k) { while (1) { if (k <= siz[ch[now][0]]) now = ch[now][0]; else if (k == siz[ch[now][0]] + 1) return now; else k -= siz[ch[now][0]] + 1, now = ch[now][1]; } } ll T, x, y, z, root; ll delta, minn, leave; int main() { T = read(); minn = read(); while (T--) { string opt; cin >> opt; ll a = read(); if (opt[0] == 'I') { if (a >= minn) { a -= delta; split(root, a, x, y); root = merge(x, merge(new_node(a), y)); } } else if (opt[0] == 'A') delta += a; else if (opt[0] == 'S') { delta -= a; split(root, minn - delta - 1, x, y); root = y; leave += siz[x]; } else if (opt[0] == 'F') { if (siz[root] < a) printf("-1\n"); else printf("%lld\n", val[kth(root, siz[root] - a + 1)] + delta); } } printf("%lld\n", leave); }
另外
1、无旋treap可以维护一棵树的中序遍历结果(此时不具有二叉搜索树的性质).但是不支持通过编号来找节点.于是在无旋treap的基础上,可以维护每个节点的父亲,这样就能通过向上边往根走边统计的方式求出一个节点是中序遍历中的第几个,从而完成各种操作
int find(int x) {//x为节点编号 int now = x, ret = siz[ch[fa[x]][0]] + 1; while (now != rt && x) { if (ch[fa[x]][1] == x) ret += siz[ch[fa[x]][0]] + 1; x = fa[x]; } return ret;//返回这个节点是中序遍历中的第几个 }