Forever Young

洛谷 P3369 【模板】普通平衡树

写平衡树真的是要自闭……一个多小时终于写完+调完了(或许我是一区\(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) << '\n';
		else if(opt == 4) cout << num(root, x) << '\n';
		else if(opt == 5) ans = 0, pre(root, x), cout << tr[ans].v << '\n';
		else if(opt == 6) ans = 0, suc(root, x), cout << tr[ans].v << '\n';
	}
	return 0;
} 
posted @ 2020-01-29 16:45  Loceaner  阅读(148)  评论(0编辑  收藏  举报