P3369 [Template] Ordinary balanced tree (FHQ Treap tree construction and analysis)

topic description

You need to write a data structure (refer to the topic title) to maintain some numbers, which need to provide the following operations:

  1. insert x number
  2. Delete x numbers (if there are multiple identical numbers, only one should be deleted)
  3. Query the ranking of x number (the ranking is defined as the number of numbers smaller than the current number +1 )
  4. Query the number with rank x
  5. Find the predecessor of x (predecessor is defined as the largest number less than x)
  6. Find the successor of x (the successor is defined as the smallest number greater than x)

input format

The first line is n, indicating the number of operations, and each line of the following n lines has two numbers opt and x, and opt indicates the sequence number of the operation ( 1≤opt≤6 )

output format

For operations 3, 4, 5, 6, each line outputs a number, indicating the corresponding answer

Input and output samples

Type #1 to copy

10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

output #1 copy

106465
84185
492737

Instructions/Tips

【Data range】
For 100% data, 1≤n≤105, ∣x∣≤107

build tree:

Build the FHQTreap tree:

The final form of the FHQ Treap tree is determined by the key value and priority. Its brilliance is that all operations only use the two basic operations of splitting and merging, and the complexity of these two operations is O(log2 n).

Prerequisite knowledge:

  • C++
  • 二叉搜索树The basic properties of
  • 二叉堆

In a binary search tree, the left node is smaller than the root node, and the root node is smaller than the right node.

Using inorder traversal, you can see that it is a monotonically increasing sequence.

Binary heap:

There are large root piles and small root piles.

The big root heap is root node > left node and root node > right node; the small root heap is the opposite;

The upper layer of the large root pile and the small root pile is generally a full binary tree. At this time, the efficiency of the search will be improved, and the time complexity will be reduced. O(log2n)

With the above knowledge points:

Analyze the topic:

1. A binary tree needs to have left and right nodes.

2. There must be a key value, a random variable.

3. The size of the left and right nodes + itself (at this time, it is very useful in ranking)

code show as below:

struct Node {
	int ls, rs;   // 左右 子节点 
	int key, pri; //  key为值 pri 为随机的优先级
	int size; // 当前节点为根的子树的节点数量,用于求第k大和排名
}t[M]; 

core:

1. Split and return two trees rooted at L and R

Look at the picture first: divided into two trees, the left subtree is less than x, and the right subtree is greater than x

 Above code:

void Split(int u, int x, int& L, int& R) {
	if (u == 0) { //到达叶子,递归返回
		L = 0, R = 0;
		return;
	}

	if (t[u].key <= x) { // 本节点比x小,那么到右子树找x 
		L = u;           // 左树的根是本节点  // 下一个如果到 这来 上一个的rs 为 这个节点u ,因为u.rs的全部字节点都大于 u的值
		Split(t[u].rs, x, t[u].rs, R); // 通过rs传回新的子节点  
	}
	else {
		R = u; // 根节点 
		Split(t[u].ls, x, L, t[u].ls);
	}
	Update(u);
}

Returns the roots L and R of the two trees.

2. Merge two subtrees

This is the inverse of splitting; doing it with random values ​​ensures that the height of the tree is kept small, which is why random values ​​are introduced.

First take the small root heap as an example, and merge two trees as shown in the figure.

 Here I use the big root heap as an example:

int Merge(int L, int R) {
	if (L == 0 || R == 0) {
		return L + R; // 左 or  右节点 
	} // 建立大顶堆
	if (t[L].pri > t[R].pri) { // 合并树  随机值 到的在右边
		t[L].rs = Merge(t[L].rs, R); // 
		Update(L);
		return L;
	}
	else {
		t[R].ls = Merge(L, t[R].ls);
		Update(R);
		return R;
	}
}

The one with the highest weight is on top.

It is much easier to write with two thoughts.

See comments for code explanation

Problem-solving code:

#include<iostream>
#include<cmath>
#include<stdlib.h>
using namespace std;
const int M = 1e6 + 10;
int cnt = 0, root = 0;
struct Node {
	int ls, rs;   // 左右 子节点 
	int key, pri; //  key为值 pri 为随机的优先级
	int size; // 当前节点为根的子树的节点数量,用于求第k大和排名
}t[M]; 
void newNode(int x) {
	cnt++;
	t[cnt].size = 1;
	t[cnt].ls = t[cnt].rs = 0;
	t[cnt].key = x;
	t[cnt].pri = rand();
}
void Update(int u) {
	t[u].size = t[t[u].ls].size + t[t[u].rs].size+1;
}
void Split(int u, int x, int& L, int& R) {
	if (u == 0) { //到达叶子,递归返回
		L = 0, R = 0;
		return;
	}

	if (t[u].key <= x) { // 本节点比x小,那么到右子树找x 
		L = u;           // 左树的根是本节点  // 下一个如果到 这来 上一个的rs 为 这个节点u ,因为u.rs的全部字节点都大于 u的值
		Split(t[u].rs, x, t[u].rs, R); // 通过rs传回新的子节点  
	}
	else {
		R = u; // 根节点 
		Split(t[u].ls, x, L, t[u].ls);
	}
	Update(u);
}

int Merge(int L, int R) {
	if (L == 0 || R == 0) {
		return L + R; // 左 or  右节点 
	} // 建立大顶堆
	if (t[L].pri > t[R].pri) { // 合并树  随机值 到的在右边
		t[L].rs = Merge(t[L].rs, R); // 
		Update(L);
		return L;
	}
	else {
		t[R].ls = Merge(L, t[R].ls);
		Update(R);
		return R;
	}
}

void Insert(int x) {
	int L, R; // 左右根的节点
	Split(root, x, L, R); 
	newNode(x); //生成x 
	int aa = Merge(L, cnt); //合并节点,这里是生成的节点,合并左子树中
	root = Merge(aa, R); //两棵树进行合并
}
void Del(int x) {//删除节点
	int L, R, p;
	Split(root, x, L, R); //先抛出 左根的节点 小于等于x   右根  大于 x
 	Split(L, x - 1, L, p); //在进行抛 右节点一定为 x 以p为根 ,左节点一定小于 x的,
	p = Merge(t[p].ls, t[p].rs); //我们只需将连接左右儿子,根节点就会被抛弃 
	root = Merge(Merge(L, p), R); // 合并左右子树
}

void Rank(int x) {//计算x的排名
	int L, R;
	Split(root, x - 1, L, R);
	printf("%d\n", t[L].size + 1); //左节点 + 1
	root = Merge(L, R);
}

int kth(int u, int k) { //计算排名为k的点 这个要和上面弄清楚
	if (k == t[t[u].ls].size + 1) {
		return u;
	}
	if (k <= t[t[u].ls].size) {
		return kth(t[u].ls, k);
	}
	if (k > t[t[u].ls].size) {  // 这里是 ls 不是 r 遍历右子树
		return kth(t[u].rs, k - t[t[u].ls].size - 1);
	}
}
void Precursor(int x) {
	int L, R;
	Split(root, x - 1, L, R);
	printf("%d\n", t[kth(L, t[L].size)].key);//这个size是节点的个数 刚好就是排名最后的点的位置
	root = Merge(L, R);
}
void Successor(int x) { //右子树的第一个点
	int L, R;
	Split(root, x, L, R);
	printf("%d\n", t[kth(R, 1)].key); // 和上面同理
	root = Merge(L, R);
}

int main() {
	srand(time(NULL));
	int n;
	cin >> n;
	while (n--) {
		int opt, x;
		cin >> opt >> x;
		switch (opt) {
		case 1:Insert(x); break;
		case 2:Del(x); break;
		case 3:Rank(x); break;
		case 4:printf("%d\n", t[kth(root, x)].key); break;
		case 5:Precursor(x); break;
		case 6:Successor(x); break;
		}
	}
	return 0;
}

Guess you like

Origin blog.csdn.net/zhi6fui/article/details/130108810