【题目来源】
https://www.luogu.com.cn/problem/P3369
https://www.acwing.com/problem/content/255/
【题目描述】
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
1.插入 x 数
2.删除 x 数(若有多个相同的数,因只删除一个)
3.查询 x 数的排名(排名定义为比当前数小的数的个数 +1)
4.查询排名为 x 的数
5.求 x 的前驱(前驱定义为小于 x,且最大的数)
6.求 x 的后继(后继定义为大于 x,且最小的数)
【输入格式】
第一行为 n,表示操作的个数。
下面 n 行每行有两个数 opt 和 x,opt 表示操作的序号(1≤opt≤6)。
【输出格式】
对于操作 3,4,5,6,每行输出一个数,表示对应答案。
【数据范围】
对于100%的数据,1≤n≤10^5,∣x∣≤10^7。
【算法分析】
替罪羊树(Scapegoat Tree),作为最简单的一种维护二叉树平衡的BST,采用简单而暴力的“摧毁、重建”方法维护二叉树的平衡:
1.若二叉树的某棵子树不平衡了,先求得这棵子树的中序遍历序列,然后再删除这棵子树。谓之“摧毁”。→ 形容为“拍平”。
2.以第1步得到的中序遍历序列的中间元素为根,构建一棵平衡的子树。谓之“重建”。→ 形容为“拎起来”。
算法竞赛中常用的 BST,学习难度从小到大的是替罪羊树、笛卡尔树、Treap树、FHQ Treap树、K-D树、Splay树、LCT。
替罪羊树常用于维护K-D树的平衡。
若每插入或删除一个结点,就立刻“摧毁、重建”整棵二叉树,代价太大。因此,在代码实践中,通常维护一棵“差不多平衡”的二叉树即可。为了评估这个“差不多平衡”,引入“不平衡率α”。
设二叉树某个结点 x 的子树总结点数为 n ,左右子树的结点数分别为 le,ri ,若 max(le,ri)/n>α ,即 max(le,ri)/(le+ri)>α,我们就“摧毁、重建”当前子树。否则,认为这棵子树是平衡的,不进行任何操作。
通常选择不平衡率α的值为 0.7,这是因为经验表明,当选用不平衡率α的值为 0.7时,二叉树的重建次数较少,二叉树的深度也较小。
【算法代码】
#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://oi-wiki.org/ds/sgt/
https://www.cnblogs.com/lingspace/p/scapegoat-tree.html