题面
输入样例
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
输出样例
106465
84185
492737
题解(模板)
- 这里的平衡树我们说的是 treap ,treap是由BST(二叉搜索树)+ heap(堆)组成的数据结构 ,它有的性质的当然是BST和heap(这里用大根堆)所有的性质,比如:当前节点的左子树中的任何一个点的权值(key) < 当前节点的权值 ,如果有相同的权值,可以在节点 上加上 cnt 属性 (表示出现的次数) ,还有就是父节点的优先级(val)一定大于它儿子的优先级,这是大根堆的性质
- 要想建立一颗平衡树,首先定义节点及其属性,一般节点中存放 题中所需信息,具体根据题中要求,这里得val其实是为了优化树的高度,如图我们的二叉树最坏的情况是可以退化成一个链表,那么它的复杂度就变成了O(n) ,但是我们如果加入了val优先级(大根堆),那么就可以降低树的高度,而且是不影响二叉树的性质的
//定义节点的属性
struct Node {
int l, r; //此节点的左右儿子节点
int key; //二叉树的权值
int val; //堆的优先级
int cnt; //当前值的数量
int size;//当前节点子树中节点的数量(包括自己)
} tr[N];
//新建一个节点
int get_node(int key) {
tr[++idx].key = key; //为其赋值
tr[idx].val = rand(); //随机生成一个优先级
tr[idx].cnt = 1; //值得数量为1(自己)
tr[idx].size = 1; //开始肯定是叶子节点,没有左右儿子,只有自己,为1
return idx;
}
//由儿子的信息来维护父节点的信息
void pushup(int p) {
tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + tr[p].cnt;
}
- (主要操作) 左/右旋 :在treap 的左右旋还是比较简单的:首先先介绍左右旋的性质 ,旋转后中序遍历顺序是不变的,还有就是左右旋其实是一个对称的过程我们看图以右旋为例讲解,右旋就是将y节点变为根节点,x 节点就变成了 y 的 右儿子 ,(将左儿子变为根节点)中序遍历不变,y的左子树在旋转完后任然是y的左子树,y的右子树 z 在旋转前是大于y节点小于x节点的,所以在旋转完应该变为 x 的左子树, x 的右子树不变
右旋代码: 因为我们在旋转之后改变了节点中的值,所以应该传入指针p, q 节点就是p的左儿子,看图旋转之后哪些子树的位置发生了改变就写哪部分: p的左儿子就等于q的右儿子,q的右儿子就是p ,然后指针 p 指向 q ,因为我们旋转后节点位置发生了变化,所以也要相应的更新节点中的属性 ,因为左右旋是对称的,所以代码只是左右不一样,自行查看即可
void zig(int &p) // 右旋
{
int q = tr[p].l;
tr[p].l = tr[q].r, tr[q].r = p, p = q; //旋转看图操作
pushup(tr[p].r); //现在是旋转完后,p的右儿子是x节点
pushup(p); //p是y节点
}
void zag(int &p) // 左旋
{
int q = tr[p].r;
tr[p].r = tr[q].l, tr[q].l = p, p = q;
pushup(tr[p].l), pushup(p);
}
- 建立treap ,为了防止越界等一些情况,我们先设置两个哨兵节点,确保以后的插入的节点都在其中,我们初始化树先让根节点等于这个1号点,然后让2号点作为1号点的右儿子,,因为我们的val是随机的,所以如果2号点(儿子)的val大于1号点(父亲),我们还要左旋一下,把val大的作为父节点,这里要注意一下,因为我们在建treap的过程中增加了哨兵节点,所以在算排名的时候要将哨兵节点排除
//建平衡树(treap)
void build() {
get_node(-INF);
get_node(INF);
root = 1; //让根节点等于1号点(-INF)
tr[1].r = 2; //1号点的右儿子等于2号点
pushup(root);
if (tr[1].val < tr[2].val) zag(root);
}
- 插入操作:从根节点开始,如果这个节点不存在,我们就新建一个节点,如果存在,看插入的值是否等于当前节点的值,如果等于,只需要给当前节点的cnt++,就表示这个值的数量+1,如果是插入的值小于当前节点的值,那么就要继续去当前节点的左子树中找,当然在插入完之后,还要维护树的高度,就是优先级,因为是在左子树中插入,那么就看左子树的值是否大于根节点的值,如果大于,就应该让左儿子成为父节点,就是对应的右旋
//插入操作
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);
//右旋之后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);
}
- 查询数值x的排名(若有多个相同的数,应输出最小的排名) 本题中数据保证结果一定存在,所以!p可以不写,从根节点开始往下遍历,如果当前节点的值>x,就应该去左子树中找(因为二叉树中当前的节点的有子树肯定是比当前节点的值大,那么就更大于x),如果是当前节点的值 = x ,那么就找了,就返回,当前节点的左子树中所有节点的数量+1(因为左子树中所有节点的值肯定是小于x的,然后题中要求输出最小,那么+1即可),如果当前节点的值是小于x的,说明就要去右子树中查找,那么最终返回的结果应该是左子树中的所有节点数量+当前节点中值的数量+右子树中找到的数量三部分
//查询数值x的排名(若有多个相同的数,应输出最小的排名)。
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 (key < tr[p].key) {
return get_rank_by_key(tr[p].l, key);
} else {
return tr[tr[p].l].size + tr[p].cnt + get_rank_by_key(tr[p].r, key);
}
}
- 查询排名为x的数值 :从根节点开始向下找,如果左子树中的数量>=rank , 说明这个x就在左子树中,继续递归即可,如果是左子树中数量不够,但是加上当前节点的cnt值就够了,那么这个x就是当前节点的值,如果加上还不够,说明在还要在右子树中找,那么此时数量应该减去左子树中的数量和当前节点值的数量
//查询排名为x的数值
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);
else if (tr[tr[p].l].size + tr[p].cnt >= rank) return tr[p].key;
else return get_key_by_rank(tr[p].r, rank - tr[tr[p].l].size - tr[p].cnt);
}
- 找前驱(严格小于key的最大数)/后继(严格大于key的最小数):找前驱,从根节点开始向下,如果说当前节点的值已经大于x,说明要找的值在左子树中(只有左子树中才是小于当前节点值的数),==如果当前节点的值小于x,我们就要从右子树和当前节点中找一个最接近当前值且比它小的数 ==,,后继同理
//找前驱(严格小于key的最大数)
int get_prev(int p, int key) {
if (!p) return -INF;
if (tr[p].key >= key) return get_prev(tr[p].l, key);
else return max(tr[p].key, get_prev(tr[p].r, key));
}
//找后继(严格大于key的最小数)
int get_next(int p, int key) {
if (!p) return INF;
if (tr[p].key <= key) return get_next(tr[p].r, key);
else return min(tr[p].key, get_next(tr[p].l, key));
}
代码
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10, INF = 1e8;
int n;
int root, idx;
struct Node {
int l, r;
int key, val;
int cnt;
int size;
} tr[N];
//由儿子的信息来维护父节点的信息
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 = 1;
tr[idx].size = 1;
return idx;
}
void zig(int &p) // 右旋
{
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) // 左旋
{
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() {
get_node(-INF);
get_node(INF);
root = 1;
tr[1].r = 2;
pushup(root);
if (tr[1].val < tr[2].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);
//右旋之后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);
}
//查询数值x的排名(若有多个相同的数,应输出最小的排名)。
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 (key < tr[p].key) {
return get_rank_by_key(tr[p].l, key);
} else {
return tr[tr[p].l].size + tr[p].cnt + get_rank_by_key(tr[p].r, key);
}
}
//查询排名为x的数值
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);
else if (tr[tr[p].l].size + tr[p].cnt >= rank) return tr[p].key;
else return get_key_by_rank(tr[p].r, rank - tr[tr[p].l].size - tr[p].cnt);
}
//找前驱(严格小于key的最大数)
int get_prev(int p, int key) {
if (!p) return -INF;
if (tr[p].key >= key) return get_prev(tr[p].l, key);
else return max(tr[p].key, get_prev(tr[p].r, key));
}
//找后继(严格大于key的最小数)
int get_next(int p, int key) {
if (!p) return INF;
if (tr[p].key <= key) return get_next(tr[p].r, key);
else return min(tr[p].key, get_next(tr[p].l, key));
}
int main() {
build();
cin >> n;
while (n--) {
int opt, x;
cin >> opt >> x;
if (opt == 1) insert(root, x); //插入操作
else if (opt == 2) remove(root, x); //删除操作
else if (opt == 3) cout << get_rank_by_key(root, x) - 1 << endl; //查询数值x的排名(若有多个相同的数,应输出最小的排名)。
else if (opt == 4) cout << get_key_by_rank(root, x + 1) << endl; //查询排名为x的数值
else if (opt == 5) cout << get_prev(root, x) << endl; //找前驱
else cout << get_next(root, x) << endl; //找后继
}
return 0;
}