题目链接:https://www.luogu.com.cn/problem/P3369
题目描述
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
插入 x 数
删除 x 数(若有多个相同的数,因只删除一个)
查询 x 数的排名(排名定义为比当前数小的数的个数 +1 )
查询排名为 x 的数
求 x 的前驱(前驱定义为小于 x,且最大的数)
求 x 的后继(后继定义为大于 x,且最小的数)
输入格式
第一行为 n,表示操作的个数,下面 n 行每行有两个数 opt 和 x,\opt 表示操作的序号( 1≤opt≤6 )
输出格式
对于操作 3,4,5,6每行输出一个数,表示对应答案
Intput
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
Output
106465
84185
492737
对于 100%的数据,1≤n≤10 5,∣x∣≤107
基于树的查找法是将待查表组织成特定树的形式并在树结构上实现查找的方法,故又称为树表式查找法,主要包括二叉排序树、平衡二叉树和B树。
- 替罪羊树满足二叉排序树(二叉搜索树)的结构特点
二叉排序树:
若左子树非空,则左子树上所有结点的值均小于根结点的值。
若右子树非空,则右子树上所有结点的值均大于(或大于等于)根结点的值。
它的左右子树也分别为二叉排序树
替罪羊树满足平衡二叉排序树的结构特点
平衡二叉排序树
左子树与右子树的高度之差的绝对值小于等于1
左子树和右子树也是平衡二叉排序树
- 替罪羊树的基本操作: 增、删、查、重构
插入x
删除x
查询x的排名
查询排名为x的数
求x的前驱,后继
- 核心操作:重构
(1)失衡的判断: 宏定义alpha,若以**x为根的树的尺寸乘以alpha大于max(左子树的尺寸,右子树的尺寸)**则进行重构。
(2)将整棵树压缩为一条链(这条链为这颗失衡树的中序遍历)
(3)以中间的结点mid为根,重新构造二叉树
- 删除的特别性:标记
在进行删除操作时,并不是真的删除结点,而是引入del标记,del
为1表示这个结点存在,del为0表示这个结点不存在。
删除结点时,树的尺寸对应的也要发生变化,引入tot、Size分别表示子树的实际大小和初始大小。
tot包含:未删除的结点+删除的结点
Size包含:未删除的结点
- 内存池存储未使用的结点标号提高效率
for(int i=1000000; i>=1; i--)
stk[++p]=i;///stk:下标从1开始,stk[1]存储的结点编号为100000
1.插入x
bool is_build(int x)
{
if((double)tree[x].Size*alpha<=(double)max(tree[tree[x].l].Size,tree[tree[x].r].Size))
return true;
return false;
}
void Inorderdfs(int now)
{
if(now!=0)
{
Inorderdfs(tree[now].l);
if(tree[now].del)///此结点存在就存入中序遍历的数组,不存在就存入内存池
inorder[++cnt]=now;
else
stk[++p]=now;
Inorderdfs(tree[now].r);
}
}
void build(int l,int r,int &now)
{
int mid=(l+r)>>1;
now=inorder[mid];
if(l==r)
{
tree[now].l=0,tree[now].r=0;
tree[now].Size=1,tree[now].tot=1;
tree[now].del=1;
return;
}
if(l<mid)
build(l,mid-1,tree[now].l);
else
tree[now].l=0;
build(mid+1,r,tree[now].r);
tree[now].Size=tree[tree[now].l].Size+tree[tree[now].r].Size+1;
tree[now].tot=tree[tree[now].l].tot+tree[tree[now].r].tot+1;
}
void rebuild(int &now)///引用
{
cnt=0;
Inorderdfs(now);///中序遍历
if(cnt)
build(1,cnt,now);
else
now=0;
}
void Insert(int &now,int val)///root是引用,形参变化,实参也变化
{
if(now==0)
{
now=stk[p--];///新开辟一个结点
tree[now].val=val;
tree[now].l=0,tree[now].r=0;
tree[now].Size=1,tree[now].tot=1;
tree[now].del=1;
return;
}
tree[now].Size++;
tree[now].tot++;
if(tree[now].val>val)
Insert(tree[now].l,val);
else
Insert(tree[now].r,val);
if(is_build(now))///是否需要重构
rebuild(now);
}
int main()
{
Insert(root,x);///插入x,每次都从根节点开始
}
2.删除x
int find_rank(int k)
{
int now=root;
int ans=1;
while(now)
{
if(tree[now].val>=k)
now=tree[now].l;
else
{
ans+=tree[tree[now].l].Size+tree[now].del;///左子树的尺寸+自身
now=tree[now].r;
}
}
return ans;
}
void delet(int &now,int k)
{
if(tree[now].del&&tree[tree[now].l].Size+1==k)
{
tree[now].del=0;///删除这个结点
tree[now].Size--;///对应的子树的实际大小减1
return;
}
tree[now].Size--;
if(tree[tree[now].l].Size+tree[now].del>=k)///左子树的大小已经超过k
delet(tree[now].l,k);///从左子树的左子树继续找
else
delet(tree[now].r,k-tree[tree[now].l].Size-tree[now].del);
}
void Delet(int k)
{
delet(root,find_rank(k));///找出k的排名rank,删除排名为rank的数
if((double)tree[root].tot*alpha>=(double)tree[root].Size)
rebuild(root);
}
int main()
{
Delet(x);///删除x
}
完整代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e6+10;
#define alpha 0.75///重构因子
struct Treapbst
{
int l,r;
int val;///权值
int Size,tot;///子树的实际大小(未删除的结点) 子树的大小(未删除的结点+删除的结点)
int del;///删除标志 删除为0,否则为1
} tree[N];
int inorder[N],stk[N],root,p,cnt;///中序遍历 内存池:存储无用的结点编号
bool is_build(int x)
{
if((double)tree[x].Size*alpha<=(double)max(tree[tree[x].l].Size,tree[tree[x].r].Size))
return true;
return false;
}
void Inorderdfs(int now)
{
if(now!=0)
{
Inorderdfs(tree[now].l);
if(tree[now].del)
inorder[++cnt]=now;
else
stk[++p]=now;
Inorderdfs(tree[now].r);
}
}
void build(int l,int r,int &now)
{
int mid=(l+r)>>1;
now=inorder[mid];
if(l==r)
{
tree[now].l=0,tree[now].r=0;
tree[now].Size=1,tree[now].tot=1;
tree[now].del=1;
return;
}
if(l<mid)
build(l,mid-1,tree[now].l);
else
tree[now].l=0;
build(mid+1,r,tree[now].r);
tree[now].Size=tree[tree[now].l].Size+tree[tree[now].r].Size+1;
tree[now].tot=tree[tree[now].l].tot+tree[tree[now].r].tot+1;
}
void rebuild(int &now)///引用
{
cnt=0;
Inorderdfs(now);///中序遍历
if(cnt)
build(1,cnt,now);
else
now=0;
}
void Insert(int &now,int val)///root是引用,形参变化,实参也变化
{
if(now==0)
{
now=stk[p--];///新开辟一个结点
tree[now].val=val;
tree[now].l=0,tree[now].r=0;
tree[now].Size=1,tree[now].tot=1;
tree[now].del=1;
return;
}
tree[now].Size++;
tree[now].tot++;
if(tree[now].val>val)
Insert(tree[now].l,val);
else
Insert(tree[now].r,val);
if(is_build(now))///是否需要重构
rebuild(now);
}
int find_rank(int k)
{
int now=root;
int ans=1;
while(now)
{
if(tree[now].val>=k)
now=tree[now].l;
else
{
ans+=tree[tree[now].l].Size+tree[now].del;///左子树的尺寸+自身
now=tree[now].r;
}
}
return ans;
}
void delet(int &now,int k)
{
if(tree[now].del&&tree[tree[now].l].Size+1==k)
{
tree[now].del=0;///删除这个结点
tree[now].Size--;///对应的子树的实际大小减1
return;
}
tree[now].Size--;
if(tree[tree[now].l].Size+tree[now].del>=k)///左子树的大小已经超过k
delet(tree[now].l,k);///从左子树的左子树继续找
else
delet(tree[now].r,k-tree[tree[now].l].Size-tree[now].del);
}
void Delet(int k)
{
delet(root,find_rank(k));///找出k的排名rank,删除排名为rank的数
if((double)tree[root].tot*alpha>=(double)tree[root].Size)
rebuild(root);
}
int find_kth(int k)///查询排名为x的数
{
int now=root;
while(now)
{
if(tree[now].del&&tree[tree[now].l].Size+1==k)
return tree[now].val;
else if(tree[tree[now].l].Size>=k)
now=tree[now].l;
else
{
k-=tree[tree[now].l].Size+tree[now].del;
now=tree[now].r;
}
}
return tree[now].val;
}
int main()
{
int t;
scanf("%d",&t);
for(int i=1000000; i>=1; i--)
stk[++p]=i;///stk:下标从1开始,stk[1]存储的结点编号为100000
while(t--)
{
int op,x;
scanf("%d%d",&op,&x);
if(op==1)
Insert(root,x);///插入x,每次都从根节点开始
if(op==2)
Delet(x);///删除x
if(op==3)
printf("%d\n",find_rank(x));
if(op==4)
printf("%d\n",find_kth(x));
if(op==5)///求x的前驱
printf("%d\n",find_kth(find_rank(x)-1));///找到x的排名rank,再找排名为rank-1的数
if(op==6)
printf("%d\n",find_kth(find_rank(x+1)));///x+1可以表示为大于x且最小的数
}
return 0;
}
学习才是消除无聊,感受充实最好的办法