【题目来源】
https://www.luogu.com.cn/problem/P3369
https://www.acwing.com/problem/content/255/
【题目描述】
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
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,所有数均在 −10^7 到 10^7 内。
【输入样例】
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 树是一种原理比较简单的 BST。Treap=Tree+Heap,即 Treap 是树和堆(大根堆或小根堆)的结合,通常翻译成树堆。
● Treap 树的操作基于“键值 val + 优先级 pri”。Treap 树的每个结点包含两个信息 (val, pri),对于 val,它是一个 BST(二叉查找树),对于 pri,它是一个堆(大根堆或小根堆)。注意这条性质与笛卡尔树的异同。
● Treap 树的核心思想就是利用优先级维护二叉树的平衡性。
● 针对 Treap 树,由于每个结点的(val,pri)可看作结点在平面上的坐标,故若每个结点的键值 val,优先级 pri 已经事先确定且不同,那么建立的 Treap 树的形态是唯一的,与结点的插入顺序没有关系。Treap 树中结点的键值 val 确定了结点在 Treap 树中的横向位置,优先级 pri 确定了结点在 Treap 树中的纵向位置。
给定键值{a,b,c,d,e,f,g},优先级{6,5,2,7,3,4,1},构建的 Treap 树如下图所示:
● 需要注意,如果预先知道所有结点的键值 val,那么在构建 Treap 树时,先对所有的键值 val 进行递增排序,然后从键值 val 最小的结点开始,从左至右逐个向树上添加结点,并依据优先级(已知或随机)在纵向上调整形态。此种情形下,构建的 Treap 树就是笛卡尔树。建树的时间复杂度为 O(n)。
而在一般情况下,并不能预先知道结点的键值 val 和优先级 pri,做法是为读入的每个新键值 val 分配一个随机的优先级,在插入树中时动态调整树的结构,使它仍然是一棵 Treap 树。建树的时间复杂度为 。
● 动态调整 Treap 树的结构,有两种方法:旋转法、FHQ 法。旋转法是经典方法,FHQ 法是近几年开始流行的新技术。FHQ 法编码简单,且能用于区间翻转、移动、持久化场合。
【算法代码】
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
int root,idx;
struct node {
int le,ri;
int val,pri;
int cnt,size;
} tr[maxn];
void pushup(int p) { //update size
tr[p].size=tr[tr[p].le].size+tr[tr[p].ri].size+tr[p].cnt;
}
int get_node(int val) { //build node
tr[++idx].val=val;
tr[idx].pri=rand();
tr[idx].cnt=tr[idx].size=1;
return idx;
}
void zig(int &p) { //rotate to right
int q=tr[p].le;
tr[p].le=tr[q].ri;
tr[q].ri=p;
p=q;
pushup(tr[p].ri);
pushup(p);
}
void zag(int &p) { //rotate to left
int q=tr[p].ri;
tr[p].ri=tr[q].le;
tr[q].le=p;
p=q;
pushup(tr[p].le);
pushup(p);
}
void build() {
get_node(-inf);
get_node(inf);
root=1;
tr[1].ri=2;
pushup(root);
if(tr[1].pri<tr[2].pri) zag(root);
}
void insert(int &p,int val) {
if(!p) p=get_node(val);
else if(tr[p].val==val) tr[p].cnt++;
else if(tr[p].val>val) {
insert(tr[p].le,val);
if(tr[tr[p].le].pri>tr[p].pri) zig(p);
} else {
insert(tr[p].ri,val);
if(tr[tr[p].ri].pri>tr[p].pri) zag(p);
}
pushup(p);
}
void erase(int &p,int val) {
if(!p) return;
if(tr[p].val==val) {
if(tr[p].cnt>1) tr[p].cnt--;
else if(tr[p].le || tr[p].ri) {
if(!tr[p].ri || tr[tr[p].le].pri>tr[tr[p].ri].pri) {
zig(p);
erase(tr[p].ri,val);
} else {
zag(p);
erase(tr[p].le,val);
}
}
else p=0;
}
else if(tr[p].val>val) erase(tr[p].le,val);
else erase(tr[p].ri,val);
pushup(p);
}
int get_rank_by_val(int p,int val) {
if(!p) return 0;
if(tr[p].val==val) return tr[tr[p].le].size+1;
if(tr[p].val>val) return get_rank_by_val(tr[p].le,val);
return tr[tr[p].le].size+tr[p].cnt+get_rank_by_val(tr[p].ri,val);
}
int get_val_by_rank(int p,int rank) {
if(!p) return 0;
if(tr[tr[p].le].size>=rank) return get_val_by_rank(tr[p].le,rank);
if(tr[tr[p].le].size+tr[p].cnt>=rank) return tr[p].val;
return get_val_by_rank(tr[p].ri,rank-tr[tr[p].le].size-tr[p].cnt);
}
int get_prev(int p,int val) {
if(!p) return -inf;
if(tr[p].val>=val) return get_prev(tr[p].le,val);
return max(tr[p].val,get_prev(tr[p].ri,val));
}
int get_next(int p,int val) {
if(!p) return inf;
if(tr[p].val<=val) return get_next(tr[p].ri,val);
return min(tr[p].val,get_next(tr[p].le,val));
}
int main() {
build();
int n;
scanf("%d",&n);
while(n--) {
int op,x;
scanf("%d %d",&op,&x);
if(op==1) insert(root,x);
else if(op==2) erase(root,x);
else if(op==3) printf("%d\n",get_rank_by_val(root,x)-1);
else if(op==4) printf("%d\n",get_val_by_rank(root,x+1));
else if(op==5) printf("%d\n",get_prev(root,x));
else printf("%d\n",get_next(root,x));
}
return 0;
}
/*
in:
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
out:
106465
84185
492737
*/
本题利用替罪羊树的代码解法如下:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
const double alpha=0.7;
struct node {
int le,ri;
int cnt,val,size;
} sgt[maxn];
int n,m;
int cnt=1;
/*
tp 表示子树中结点下标组成的线性序列
tn 表示子树中结点个数组成的线性序列
tv 表示子树中结点权值组成的线性序列
*/
vector<int> tp,tn,tv;
bool exist(int x) {
return !(sgt[x].le==0 && sgt[x].ri==0 && sgt[x].cnt==0);
}
int flatten(int x) {
if(exist(sgt[x].le)) flatten(sgt[x].le);
int id=tp.size();
if(sgt[x].cnt) {
tp.push_back(x);
tn.push_back(sgt[x].cnt);
tv.push_back(sgt[x].val);
}
if(exist(sgt[x].ri)) flatten(sgt[x].ri);
return id;
}
void rebuild(int x,int le,int ri) {
int mid=(le+ri)/2;
int sizel=0,sizer=0;
if(le<mid) {
sgt[x].le=tp[(le+mid-1)/2];
rebuild(sgt[x].le,le,mid-1);
sizel=sgt[sgt[x].le].size;
} else sgt[x].le=0;
if(ri>mid) {
sgt[x].ri=tp[(mid+1+ri)/2];
rebuild(sgt[x].ri,mid+1,ri);
sizer=sgt[sgt[x].ri].size;
} else sgt[x].ri=0;
sgt[x].cnt=tn[mid];
sgt[x].val=tv[mid];
sgt[x].size=sizel+sizer+sgt[x].cnt;
}
void restr(int x) {
double val=max(sgt[sgt[x].le].size, sgt[sgt[x].ri].size)*1.0/sgt[x].size;
if(val>alpha) {
tp.clear();
tn.clear();
tv.clear();
int id=flatten(x);
swap(tp[id],tp[(tp.size()-1)/2]);
rebuild(x,0,tp.size()-1);
}
}
void insert(int x, int val) {
if(!exist(x)) {
sgt[x].cnt=1;
sgt[x].val=val;
} else if(val<sgt[x].val) {
if(!exist(sgt[x].le)) sgt[x].le=++cnt;
insert(sgt[x].le,val);
} else if(val>sgt[x].val) {
if (!exist(sgt[x].ri)) sgt[x].ri=++cnt;
insert(sgt[x].ri,val);
} else sgt[x].cnt++;
sgt[x].size++;
restr(x);
}
void del(int x, int val) {
sgt[x].size--;
if(val<sgt[x].val) del(sgt[x].le,val);
else if (val>sgt[x].val) del(sgt[x].ri,val);
else sgt[x].cnt--;
restr(x);
}
int ord_pre(int x, int val) {
if(val<sgt[x].val)
return (exist(sgt[x].le)?ord_pre(sgt[x].le,val):0);
else if(val>sgt[x].val)
return sgt[sgt[x].le].size+sgt[x].cnt+(exist(sgt[x].ri)?ord_pre(sgt[x].ri,val):0);
else
return sgt[sgt[x].le].size;
}
int ord_nxt(int x, int val) {
if(val>sgt[x].val)
return ((exist(sgt[x].ri)?ord_nxt(sgt[x].ri,val):0));
else if(val<sgt[x].val)
return sgt[sgt[x].ri].size+sgt[x].cnt+(exist(sgt[x].le)?ord_nxt(sgt[x].le,val):0);
else
return sgt[sgt[x].ri].size;
}
int ord(int val) {
return ord_pre(1,val)+1;
}
int find(int x, int rk) {
if(rk<=sgt[sgt[x].le].size)
return find(sgt[x].le, rk);
else if(rk>sgt[sgt[x].le].size+sgt[x].cnt)
return find(sgt[x].ri, rk-sgt[sgt[x].le].size-sgt[x].cnt);
else
return sgt[x].val;
}
int pre(int x) {
int rk=ord_pre(1,x);
return find(1,rk);
}
int nxt(int x) {
int rk=ord_nxt(1,x);
return find(1,sgt[1].size-rk+1);
}
int main() {
int opt,x;
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%d%d",&opt,&x);
if(opt==1) insert(1,x);
else if(opt==2) del(1,x);
else if(opt==3) printf("%d\n",ord(x));
else if(opt==4) printf("%d\n",find(1,x));
else if(opt==5) printf("%d\n",pre(x));
else printf("%d\n",nxt(x));
}
return 0;
}
/*
in:
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
out:
106465
84185
492737
*/
【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/120380473
https://www.acwing.com/solution/content/153478/