253. 普通平衡树(平衡树,@Treap,@无旋Treap,模板题,* * )

本文介绍了如何使用Treap(结合堆和二叉搜索树特性的一种数据结构)解决一个包含插入、删除、查询前驱后继等操作的问题,它在ACWing题库中用于高效维护数值并支持特定查询操作。
摘要由CSDN通过智能技术生成

253. 普通平衡树 - AcWing题库

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

  1. 插入数值 x。
  2. 删除数值 x(若有多个相同的数,应只删除一个)。
  3. 查询数值 x 的排名(若有多个相同的数,应输出最小的排名)。
  4. 查询排名为 x 的数值。
  5. 求数值 x 的前驱(前驱定义为小于 x 的最大的数)。
  6. 求数值 x 的后继(后继定义为大于 x 的最小的数)。

注意: 数据保证查询的结果一定存在。

输入格式

第一行为 n,表示操作的个数。

接下来 n 行每行有两个数 opt 和 x,opt 表示操作的序号(1≤opt≤6)。

输出格式

对于操作 3,4,5,6 每行输出一个数,表示对应答案。

数据范围

1≤n≤100000,所有数均在 −107 到 107 内。

输入样例:
8
1 10
1 20
1 30
3 20
4 2
2 10
5 25
6 -1
输出样例:
2
20
20
20

解析: 

treap = tree+heap(相对其他树操作比较少)
红黑树(代码太长)
splay(比较难)
AVL

1 binary search tree BST 二叉搜索树
   val in root.left<root.val<val in root.right
中序遍历 是从小到大的
左中右
BST就是动态维护一个有序序列/有序集合

            5
           / \
          4   8
         /   / \
        2   6   9
       / \   \ 
      1   3   7

1 插入操作
1.1插入已有的值 以3为例
        2   
       / \    
      1   3
           \
            3
            3.cnt++
1.2插入没有的值 以10为例
            5
           / \
          4   8
         /   / \
        2   6   9
       / \   \   \
      1   3   7   10

2删除操作
2.1删除中间节点(把中间节点转为叶子节点)
2.2删除叶子节点(直接删)

3找前驱后继
3.1找前驱(前驱:中序遍历中在当前节点的前一个位置的节点)
3.1.1 如果当前节点root存在左子树  
      左子树root.left中的最大值(从root.left一直往右走)就是root的前驱
      left = root.left
      while(left.right) left = left.right
3.1.2 如果当前节点root不存在左子树
      就找父节点fa
      如果父节点是右爸爸 root=fa.left 那么root.val<fa.val 所以父节点fa不是root的前驱
          则继续看右爸爸的爸爸 fa.fa,直到出现左爸爸(当前节点是父节点右儿子)时,
          说明该父节点比当前节点以及当前节点的所有儿子都小
  (比答案小)o   o(比答案以及答案的所有子节点都大,不满足小于p)
             \ /
              o(答案,小于p的节点中的最大值)
               \ 
                o(第一个出现左爸爸的节点)
               /
              o(出发点p,只有右爸爸)
3.2找后继(后继:中序遍历中在当前节点的后一个位置的节点)

4 找最大/最小 end() begin()

1->4就是set()

5 求某个值的排名

6 求排名是k的数是哪个

7 比某个数小的最大值/比某个数大的最小值 

treap 让BST尽量随机(使得高度接近logn)

node
{
    int l,r;左儿子编号 右儿子编号
    int key,val;排序序号(BST,有序) 优先级(大根堆heap里的)
}tr[N]
root.val >= root.left.val 
root.val >= root.right.val
val取随机,则树的结构就随机(高度的平均就接近logn)

加两个哨兵
-∞  +∞ 处理边界
1 插入
    先递归到插进去再说(回溯时类似堆的回溯,如果当前p.val>fa.val 交换p和fa的位置)
    实现过程->左旋 & 右旋
     x               y
    / \  右旋zig    / \
   y   tt   ->     hh  x
  / \      <-         / \
 hh  z   左旋zag     z   tt
 hh,tt分别在整个序列的最前和最后 在旋转前后没有变化
 同时 中序遍历(递增序列)的结构在旋转前后都是 hh y z x tt 
     p               q
    / \  右旋zig    / \
   q   c   ->      a   p
  / \      <-         / \
 a   b   左旋zag     b   c
2 删除
    类似堆 把当前节点x交换到tt的位置,然后删除
    只不过在这里的操作是通过右旋
    可以发现每次右旋x就到了x.right的位置
    所以我们可以一直右旋直到x变为叶子节点,然后删除

作者:仅存老实人
链接:https://www.acwing.com/solution/content/20092/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<math.h>
#include<map>
#include<sstream>
#include<deque>
#include<unordered_map>
#include<unordered_set>
#include<bitset>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<long long, long long> PLL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
const int INF = 0x3f3f3f3f;
const LL Mod = 2147483648;
const int N = 1e5 + 10, M = N * 25;
int n;
struct Node {
	int l, r;
	int key,val;
	int cnt, size;
}tr[N];
int root, idx;

void pushup(int& p) {
	tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + tr[p].cnt;
}

int get_node(int key) {
	tr[++idx].key = key;
	tr[idx].val = rand();
	tr[idx].cnt = tr[idx].size = 1;
	return idx;
}


void zig(int& p) {//right
	int q = tr[p].l;
	tr[p].l = tr[q].r;
	tr[q].r = p;
	p = q;
	pushup(tr[p].r);
	pushup(p);
}

void zag(int& p) {//left
	int q = tr[p].r;
	tr[p].r = tr[q].l;
	tr[q].l = p;
	p = q;
	pushup(tr[p].l);
	pushup(p);
}

void build() {
	root = get_node(-INF);
	tr[root].r = get_node(INF);
	pushup(root);
	if (tr[root].val < tr[tr[root].r].val) {
		zag(root);
	}
}

void insert(int&p,int key) {
	if (!p) {
		p = get_node(key);
	}
	else if (tr[p].key == key) {
		tr[p].cnt++;
	}
	else if (tr[p].key > key) {
		insert(tr[p].l, key);
		if (tr[tr[p].l].val > tr[p].val)zig(p);
	}
	else {
		insert(tr[p].r, key);
		if (tr[tr[p].r].val > tr[p].val)zag(p);
	}
	pushup(p);
}

void remove(int&p,int key) {
	if (!p)return;
	if (tr[p].key == key) {
		if (tr[p].cnt > 1)tr[p].cnt--;
		else if (tr[p].l || tr[p].r) {
			if (!tr[p].r || tr[tr[p].l].val > tr[tr[p].r].val) {
				zig(p);
				remove(tr[p].r, key);
			}
			else {
				zag(p);
				remove(tr[p].l, key);
			}
		}
		else {
			p = 0;
		}
	}
	else if(tr[p].key>key){
		remove(tr[p].l, key);
	}
	else {
		remove(tr[p].r, key);
	}
	pushup(p);
}

int get_rank_by_key(int p,int key) {
	if (!p)return 0;
	if (tr[p].key == key) {
		return tr[tr[p].l].size + 1;
	}
	if (tr[p].key > key)return get_rank_by_key(tr[p].l, key);
	return tr[tr[p].l].size + tr[p].cnt + get_rank_by_key(tr[p].r, key);
}

int get_key_by_rank(int p,int rank) {
	if (!p)return INF;
	if (tr[tr[p].l].size >= rank)return get_key_by_rank(tr[p].l, rank);
	if (tr[tr[p].l].size + tr[p].cnt >= rank)return tr[p].key;
	return get_key_by_rank(tr[p].r, rank - tr[tr[p].l].size - tr[p].cnt);
}

int get_pre(int p, int key) {
	if (!p)return -INF;
	if (tr[p].key >= key)return get_pre(tr[p].l, key);
	return  max(tr[p].key, get_pre(tr[p].r, key));
}

int get_next(int p, int key) {
	if (!p)return INF;
	if (tr[p].key <= key)return get_next(tr[p].r, key);
	return min(tr[p].key, get_next(tr[p].l, key));
}


int main() {
	cin >> n;
	build();
	int op, x;
	for (int i = 1; i <= n; i++) {
		scanf("%d%d", &op, &x);
		if (op == 1) {
			insert(root,x);
		}
		else if (op == 2) {
			remove(root,x);
		}
		else if (op == 3) {
			printf("%d\n", get_rank_by_key(root,x)-1);
		}
		else if (op == 4) {
			printf("%d\n", get_key_by_rank(root,x+1));
		}
		else if (op == 5) {
			printf("%d\n", get_pre(root,x));
		}
		else {
			printf("%d\n", get_next(root,x));
		}
	}	
	return 0;
}

无旋Treap:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<math.h>
#include<map>
#include<sstream>
#include<deque>
#include<unordered_map>
#include<unordered_set>
#include<bitset>
#include<stdio.h>
using namespace std;
typedef long long LL;
//#define int LL
typedef unsigned long long ULL;
typedef pair<long long, long long> PLL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
const int inf = 0x3f3f3f3f;
const int INF = 0x3f3f3f3f3f3f3f3f;
const LL Mod = 998244353;
const int N = 1e5 + 10, M = 1 << 15;
int n, m;
int rt;
struct Treap {
	int son[N][2], s[N],w[N],pro[N];
	int idx;
	//bool fl[N];
	int build(int x) {
		w[++idx] = x, s[idx] = 1, pro[idx] = rand();
		return idx;
	}
	void up(int u) {
		s[u] = s[son[u][1]] + s[son[u][0]] + 1;
	}
	//void pushdown(int u) {
	//	swap(son[u][1], son[u][0]);
	//	int l = son[u][0], r = son[u][1];
	//	if (l)fl[l] ^= 1;
	//	if (r)fl[r] ^= 1;
	//	fl[u] = 0;
	//}
	int merge(int l, int r) {
		if (!l || !r) {
			return l + r;
		}
		if (pro[l] < pro[r]) {
			//if (fl[l])pushdown(l);
			son[l][1] = merge(son[l][1], r);
			up(l);
			return l;
		}
		//if (fl[r])pushdown(r);
		son[r][0] = merge(l, son[r][0]);
		up(r);
		return r;
	}
	void split(int u,int k,int &l,int&r) {
		if (!u) {
			l = r = 0;
			return;
		}
		//if (fl[u])pushdown(u);
		int &lch = son[u][0],& rch = son[u][1];
		if (k >= w[u]) {
			l = u;
			split(rch, k, rch, r);
		}
		else {
			r = u;
			split(lch, k, l, lch);
		}
		up(u);
	}
	int insert(int u, int x) {
		int l, r;
		split(u, x, l, r);
		return merge(merge(l, build(x)), r);
	}
	int delet(int u, int x) {
		int a, b, c;
		split(u, x, a, b);
		split(a, x - 1, a, c);
		c = merge(son[c][0], son[c][1]);
		return merge(merge(a, c), b);
	}
	int get_rank(int u, int x) {
		int a, b;
		split(u, x - 1, a, b);
		int ret = s[a]+1;
		merge(a, b);
		return ret;
	}
	int get_num(int u, int x) {
		while (1) {
			if (x > s[son[u][0]] + 1) {
				x -= s[son[u][0]] + 1;
				u = son[u][1];
			}
			else if (x <= s[son[u][0]]) {
				u = son[u][0];
			}
			else {
				return w[u];
			}
		}
	}	
}tr;

signed main() {

	int T;
	cin >> T;
	int x=0, y=0, z=0;
	while (T--) {
		int op, num;
		scanf("%d%d", &op, &num);
		if (op == 1) {
			rt = tr.insert(rt, num);
		}
		else if (op == 2) {
			rt=tr.delet(rt, num);
		}
		else if (op == 3) {
			printf("%d\n", tr.get_rank(rt, num));
		}
		else if(op==4) {
			printf("%d\n", tr.get_num(rt, num));
		}
		else if (op == 5) {
			int a, b;
			tr.split(rt, num - 1, a, b);
			printf("%d\n", tr.get_num(a, tr.s[a]));
			rt=tr.merge(a, b);
		}
		else {
			int a, b;
			tr.split(rt, num, a, b);
			printf("%d\n", tr.get_num(b, 1));
			rt = tr.merge(a, b);
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值