写平衡树真的是要自闭……一个多小时终于写完+调完了(或许我是一区(62)级信息组里最晚会(treap)的人了……)
发现写(treap)的题解比较少……于是自己看着黄学长的代码写了一篇,注释写的很明白,都在代码里了,不过要注意的是在做这道题之前一定要先学会二叉搜索树和堆,否则就会很难理解,这两个东西都不算难,可以自己去网上搜一下,这里就不贴链接了
下面就看代码吧(qwq)
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
using namespace std;
const int A = 1e5 + 11;
inline int read() {
char c = getchar(); int x = 0, f = 1;
for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
return x * f;
}
int n, size, root, ans;
struct data { int l, r, v, size, rnd, w; } tr[A];
//l左儿子,r右儿子,v权值,size子树大小,rnd随机数,w为当前权值的个数
void update(int rt) {
tr[rt].size = tr[tr[rt].l].size + tr[tr[rt].r].size + tr[rt].w;
//当前子树的大小等于左子树大+右子树大小+当前节点权值的个数
}
//右旋
/*
当前节点的左儿子变成右旋节点的右儿子
旋转节点的右儿子变成当前节点
旋转节点的子树大小变为当前节点的子树大小
更新当前节点子树大小
*/
void rturn(int &rt) {
int t = tr[rt].l;
tr[rt].l = tr[t].r;
tr[t].r = rt;
tr[t].size = tr[rt].size;
update(rt); rt = t;
}
//左旋
/*
当前节点的右儿子变成左旋节点的左儿子
旋转节点的左儿子变成当前节点
旋转节点的子树大小变为当前节点的子树大小
更新当前节点子树大小
*/
void lturn(int &rt) {
int t = tr[rt].r;
tr[rt].r = tr[t].l;
tr[t].l = rt;
tr[t].size = tr[rt].size;
update(rt); rt = t;
}
//插入节点
void insert(int &rt, int x) {
if(rt == 0) {//没有这种权值的节点就新建一个
rt = ++size;
tr[rt].size = tr[rt].w = 1;//因为是新建的,所以siz和w都为1
tr[rt].v = x, tr[rt].rnd = rand(); return;
}
tr[rt].size++;//如果目前节点编号不为0则让当前节点的size++
if(tr[rt].v == x) { tr[rt].w++; return; }
//如果找到了相同权值的节点就直接让当前节点相同值的个数++并直接返回
if(x > tr[rt].v) {//如果大于当前节点的权值就到右子树里寻找(二叉搜索树性质)
insert(tr[rt].r, x);
if(tr[tr[rt].r].rnd < tr[rt].rnd) lturn(rt);//维护堆性质
}
else {//否则去左儿子
insert(tr[rt].l, x);
if(tr[tr[rt].l].rnd > tr[rt].rnd) rturn(rt);//维护堆性质
}
}
//删除节点
void del(int &rt, int x) {
if(rt == 0) return; //如果rt是0说明没有找到,直接返回
if(tr[rt].v == x) { //找到啦~~
if(tr[rt].w > 1) { //如果不止一个只删除一个,相应的size也要--
tr[rt].size--, tr[rt].w--; return;
}
if(tr[rt].l * tr[rt].r == 0) rt = tr[rt].l + tr[rt].r; //有一个儿子为空
else if(tr[tr[rt].l].rnd < tr[tr[rt].r].rnd) rturn(rt), del(rt, x);
else lturn(rt), del(rt, x);
}
else if(x > tr[rt].v) tr[rt].size--, del(tr[rt].r, x);
else tr[rt].size--, del(tr[rt].l, x);
}
//x的排名
int rank(int rt, int x) {
if(rt == 0) return 0;//没有就返回0
if(tr[rt].v == x) return tr[tr[rt].l].size + 1;
//当前节点找到了,答案就是左子树大小+1,因为左子树中节点的权值都比当前节点小,右子树都比当前大
if(x > tr[rt].v) return tr[tr[rt].l].size + tr[rt].w + rank(tr[rt].r, x);
//如果当前节点权值小于x,则到右子树中寻找x,此时需要返回左儿子大小加上当前节点权值个数再加上右子树中x的排名
return rank(tr[rt].l, x);
}
//排名为x的数
int num(int rt, int x) {
if(rt == 0) return 0;//老套路
if(x <= tr[tr[rt].l].size) return num(tr[rt].l, x);
//因为二叉搜索树的性质,所以左子树的权值都比当前小,如果x小于左子树的size,就到左子树中去找
if(x > tr[tr[rt].l].size + tr[rt].w) return num(tr[rt].r, x - tr[tr[rt].l].size - tr[rt].w);
//如果x大于左子树大小与当前权值个数之和的大小 就到右子树中去找,找的时候要让x减去左子树大小和当前权值个数之和
//即到右子树中寻找第x - tr[tr[rt].l].size - tr[rt].w大的数
return tr[rt].v; //负责就是找到了,直接return
}
//这里的ans是指节点编号,最后还要输出节点的值
//查询前驱precursor(就是指小于当前值的最大值)
void pre(int rt, int x) {
if(rt == 0) return;//老套路 * 2
if(tr[rt].v < x) ans = rt, pre(tr[rt].r, x);
else pre(tr[rt].l, x);
}
//查询后继successor(就是指大于当前值的最小值)
void suc(int rt, int x) {
if(rt == 0) return;//老套路 * 3
if(tr[rt].v > x) ans = rt, suc(tr[rt].l, x);
else suc(tr[rt].r, x);
}
int main() {
n = read();
//按要求执行qwq
for(int i = 1, opt, x; i <= n; i++) {
opt = read(), x = read();
if(opt == 1) insert(root, x);
else if(opt == 2) del(root, x);
else if(opt == 3) cout << rank(root, x) << '
';
else if(opt == 4) cout << num(root, x) << '
';
else if(opt == 5) ans = 0, pre(root, x), cout << tr[ans].v << '
';
else if(opt == 6) ans = 0, suc(root, x), cout << tr[ans].v << '
';
}
return 0;
}