您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
第一行为 n,表示操作的个数,下面 n 行每行有两个数 opt 和 x,opt 表示操作的序号( 1≤opt≤6 )
对于操作 3,4,5,6 每行输出一个数,表示对应答案
输入 #1复制
10 1 106465 4 1 1 317721 1 460929 1 644985 1 84185 1 89851 6 81968 1 492737 5 493598
输出 #1复制
106465 84185 492737
【数据范围】
对于 100% 的数据,1≤n≤105,∣x∣≤107
对FHQTreap树进行构建:
FHQ Treap树是最后形态由键值和优先级决定。它的高明之处是所有操作都只用到了分裂和合并这两个基本操作,这两个操作的复杂度都为O(log2 n)。
前置知识:
C++
二叉搜索树
的基本性质,下面会讲二叉堆
二叉树搜索树是左节点小于根节点,根节点小于右节点。
用中序遍历可以看到是一个单调递增的序列。
二叉堆:
堆有大根堆和小根堆之分。
大根堆是根节点>左节点and 根节点>右节点;小根堆相反;
大根堆和小根堆一般上一层是满二叉树。这时会提高搜索的效率,时间复杂度减少。O(log2n)
有了以上的知识点:
对题目进行分析:
1、二叉树需要有左右节点。
2、要有一个键值,随机变量。
3、左右节点+本身的大小(这时在排名上有很大的用处)
代码如下:
struct Node {
int ls, rs; // 左右 子节点
int key, pri; // key为值 pri 为随机的优先级
int size; // 当前节点为根的子树的节点数量,用于求第k大和排名
}t[M];
核心:
1.分裂,返回以L和R为根的两棵树
先看图:划分为两棵树,左子树小于x,右子树大于x
上代码:
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);
}
返回两棵树的树根L和R。
2.合并两颗子树
这是分裂的逆过程;用随机值进行可以确保树的高度比较小,这就是为什么要引入随机值的原因。
先以小根堆为例,如图所示合并两棵树。
我这里用了大根堆为例:
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;
}
}
权值大的在上面。
有着两种思想就好写多了。
代码解释请看注释
#include
#include
#include
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;
}