题解:洛谷 P3369 【模板】普通平衡树
【题目来源】
【题目描述】
您需要动态地维护一个可重集合 \(M\),并且提供以下操作:
- 向 \(M\) 中插入一个数 \(x\)。
- 从 \(M\) 中删除一个数 \(x\)(若有多个相同的数,应只删除一个)。
- 查询 \(M\) 中有多少个数比 \(x\) 小,并且将得到的答案加一。
- 查询如果将 \(M\) 从小到大排列后,排名位于第 \(x\) 位的数。
- 查询 \(M\) 中 \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)。
- 查询 \(M\) 中 \(x\) 的后继(后继定义为大于 \(x\),且最小的数)。
对于操作 \(3,5,6\),不保证当前可重集中存在数 \(x\)。
对于操作 \(5,6\),保证答案一定存在。
【输入】
第一行为 \(n\),表示操作的个数,下面 \(n\) 行每行有两个数 \(\text{opt}\) 和 \(x\),\(\text{opt}\) 表示操作的序号($ 1 \leq \text{opt} \leq 6 $)。
【输出】
对于操作 \(3,4,5,6\) 每行输出一个数,表示对应答案。
【输入样例】
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
【输出样例】
106465
84185
492737
【算法标签】
《洛谷 P3369 普通平衡树》 #平衡树# #模板题#
【代码详解】
// 使用FHQ Treap再写一遍
#include <bits/stdc++.h>
using namespace std;
const int N = 100005; // 数组最大容量
int n; // 操作次数
// Treap节点结构体
struct Node
{
int l, r; // 左右儿子节点编号
int val; // 节点权值
int key; // 堆的随机值
int size; // 子树大小
}tr[N];
int root; // 根节点编号
int idx; // 节点个数计数器
// 创建新节点
int newnode(int v)
{
tr[++idx].val = v;
tr[idx].key = rand();
tr[idx].size = 1;
return idx;
}
// 更新节点信息
void pushup(int p)
{
tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + 1;
}
// 分裂:将Treap按值v分裂为两个Treap
void split(int p, int v, int &x, int &y)
{
if (!p)
{
x = y = 0;
return ;
}
if (tr[p].val <= v)
{
x = p;
split(tr[x].r, v, tr[x].r, y);
}
else
{
y = p;
split(tr[y].l, v, x, tr[y].l);
}
pushup(p);
}
// 合并:将两个Treap合并
int merge(int x, int y)
{
if (!x || !y) // 如果有一个为空,返回另一个
{
return x + y;
}
if (tr[x].key < tr[y].key) // 维护堆性质
{
tr[x].r = merge(tr[x].r, y);
pushup(x);
return x;
}
else
{
tr[y].l = merge(x, tr[y].l);
pushup(y);
return y;
}
}
// 插入值v
void insert(int v)
{
int x, y, z;
split(root, v, x, y); // 按v分裂
z = newnode(v); // 创建新节点
root = merge(merge(x, z), y); // 合并
}
// 删除值v
void del(int v)
{
int x, y, z;
split(root, v, x, z); // 按v分裂
split(x, v - 1, x, y); // 按v-1分裂,得到包含v的子树y
y = merge(tr[y].l, tr[y].r); // 删除y的根节点
root = merge(merge(x, y), z); // 合并
}
// 返回第k个节点
int get_k(int p, int k)
{
if (k <= tr[tr[p].l].size)
{
return get_k(tr[p].l, k);
}
if (k == tr[tr[p].l].size + 1)
{
return p;
}
return get_k(tr[p].r, k - tr[tr[p].l].size - 1);
}
// 查找v的前驱
void get_pre(int v)
{
int x, y;
split(root, v - 1, x, y); // 按v-1分裂
int p = get_k(x, tr[x].size); // 找到x中最大的节点
cout << tr[p].val << endl;
root = merge(x, y); // 合并
}
// 查找v的后继
void get_suc(int v)
{
int x, y;
split(root, v, x, y); // 按v分裂
int p = get_k(y, 1); // 找到y中最小的节点
cout << tr[p].val << endl;
root = merge(x, y); // 合并
}
// 查询v的排名
void get_rank(int v)
{
int x, y;
split(root, v - 1, x, y); // 按v-1分裂
cout << tr[x].size + 1 << endl; // 小于v的节点数+1
root = merge(x, y); // 合并
}
// 查询第k小的值
void get_val(int k)
{
int p = get_k(root, k);
cout << tr[p].val << endl;
}
int main()
{
cin >> n; // 读入操作次数
srand(time(0)); // 初始化随机数种子
while (n--)
{
int op, x;
cin >> op >> x;
if (op == 1) // 插入
{
insert(x);
}
if (op == 2) // 删除
{
del(x);
}
if (op == 3) // 查询排名
{
get_rank(x);
}
if (op == 4) // 查询第k小
{
get_val(x);
}
if (op == 5) // 查询前驱
{
get_pre(x);
}
if (op == 6) // 查询后继
{
get_suc(x);
}
}
return 0;
}
【运行结果】
10
1 106465
4 1
106465
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
84185
1 492737
5 493598
492737
浙公网安备 33010602011771号