1 条题解
-
0mengqingyu LV 10 @ 2024-12-21 18:47:04
点个赞
#include<iostream> #include<cstring> #include<random> #include<algorithm> using namespace std; const int N=1e5+10; std::mt19937 rnd(233); //高性能的随机函数 种子取233 struct Node{ int l,r; //左孩子节点的编号 右孩子节点的编号 编号其实就是下标 int val; //该节点内的权值 int key; //该节点的随机索引 即随机优先级 int size; //以该节点为根的所形成的树的大小 }tr[N]; //idx给每个节点分配的编号(下标) 最后它记录的就是这棵树中节点的总个数 //root记录的就是这棵树的根节点 int idx,root; //x树 y树 z树 后面的操作需要用到 先定义为全局变量 int x,y,z; //建立新节点 给该节点分配编号 同记录信息 inline int newnode(int val) { tr[++idx].val=val; //记录该节点的值为val tr[idx].key=rnd(); //随机生成该节点的优先级 tr[idx].size=1; //以该节点为根的树的大小为1 return idx; //返回分配给该节点的编号idx } //更新节点的信息 记录以该节点u为根的树的大小 inline void update(int u) { tr[u].size=tr[tr[u].l].size+tr[tr[u].r].size+1; } //分裂操作:按val值将u树分裂成x树和y树 //这里用 引用 将分裂得到的x树和y树 返回回去 void split(int u,int val,int &x,int &y) { //如果要分裂的当前这棵u树是不存在的 那么不可能分裂出x树和y树 //所以把x和y都设为0 if(!u) x=y=0; else { //如果当前节点的值<=val 根据二叉搜索树的性质 该节点的左子树肯定都<=val //但是当前节点的右子树中存在大于val的节点 因此我们需要对右子树再进行递归分裂 if(tr[u].val<=val) { x=u; //按val值将tr[u].r右子树分裂成 tr[u].r树 和y树 split(tr[u].r,val,tr[u].r,y); } else { y=u; //按val值将tr[u].l左子树分裂成x树和tr[u].l树 split(tr[u].l,val,x,tr[u].l); } //分裂完了之后 要记得更新节点信息 update(u); } } //将x树和y树合并 返回的结果是这两棵树合并后的那个根节点 int merge(int x,int y) { //如果x树不存在y树存在 那么合并后就只有y树 因此返回y树 //如果y树不存在x树存在 那么合并后就只有x树 因此返回x树 if(!x||!y) return x+y; //这里当作是大根堆来处理 写成> 或者>=都可以 //也可以当作小根堆来处理 即写成< <= 那么就是小根堆 if(tr[x].key>tr[y].key) { //根据二叉堆的大根堆性质tr[x].key>tr[y].key 那么x在y的上方 //根据二叉搜索树的性质,我们必须保证x树中的值<=y树中的值 所以y树在右子树 //综上 y树是x的右子树 因此我们把y树和x原来的右子树合并 形成 x树新的右子树 tr[x].r=merge(tr[x].r,y); //更新以x为根的树的大小信息 update(x); //将y树合并到x树 合并后根节点就是x return x; } else { //根据二叉堆的大根堆性质tr[y].key>tr[x].key 那么y在x的上方 //根据二叉搜索树的性质,我们必须保证x树中的值<=y树中的值 所以x树在左子树 //综上 x树是y的左子树 因此我们把x树和y原来的左子树合并 形成 y树新的左子树 tr[y].l=merge(x,tr[y].l); //更新以y为根的树的大小信息 update(y); //将x树合并到y树 合并后根节点就是y return y; } } //插入 void insert(int val) { //先按值val分裂成x树和y树 split(root,val,x,y); //然后新生成节点newnode(val) 把这个节点和x树合并 得到一棵树 //我们再将这棵树和y树合并 就得到了插入val后的完整的新树 root=merge(merge(x, newnode(val)),y); } //删除 void del(int val) { //第一步:按值val将root树分裂成x树和z树 分裂完了之后x树都是<=val z树都是>val split(root,val,x,z); //第二步:按值val-1将x树分裂成x树和y树 分裂完了之后x树都是<=val-1 y树都是>val-1 也就是说y树都是=val split(x,val-1,x,y); //第三步:将y的左子树和右子树合并 它俩众叛亲离偷偷合并抛弃了父节点y 那么就相当于删除了y这个根 //然后我们把它俩合并后得到的新树的根节点赋值给y y=merge(tr[y].l,tr[y].r); //第四步:将x树和 合并后得到的新树y合并 又得到一个新树 //第五步:再将这棵新树和z树合并 最终得到将val删除后的新树 root=merge(merge(x,y),z); } //查询val值的排名 void getrank(int val) { //按值val-1将root树分裂成x树和y树 那么此时x树中节点值都是<=val-1的 split(root,val-1,x,y); //统计一下x树中的大小(即可知道有多少个数比val小) // 有tr[x].size个数比val小,然后再+1 那么就是val值的排名了 printf("%d\n",tr[x].size+1); //有分裂一定有合并 分离完了要记得合并回去 root=merge(x,y); } //查询排名为rank对应的大数 void getnum(int rank) { int u=root; while(u) { if(tr[tr[u].l].size+1==rank) //找到排名为rank所对应的数 就可以退出了 break; else if(tr[tr[u].l].size>=rank) //在左子树中 u=tr[u].l; else //在右子树中 { rank-=tr[tr[u].l].size+1; u=tr[u].r; } } printf("%d\n",tr[u].val); } //寻找前驱 void pre(int val) { //按值val-1将root数分裂成x树(左子树)和y树(右子树) split(root,val-1,x,y); int u=x; //找到x树中的最右节点 则它就是val的前驱 while(tr[u].r) u=tr[u].r; printf("%d\n",tr[u].val); //有分裂一定有合并 分离完了要记得合并回去 root=merge(x,y); } //寻找后继 void nxt(int val) { //按值val将root数分裂成x树(左子树)和y树(右子树) split(root,val,x,y); int u=y; //找到y树中的最左节点 则它就是val的后继 while(tr[u].l) u=tr[u].l; printf("%d\n",tr[u].val); //有分裂一定有合并 分离完了要记得合并回去 root=merge(x,y); } int main() { int T; scanf("%d",&T); while(T--) { int op,x; scanf("%d%d",&op,&x); if(op==1) //插入 insert(x); else if(op==2) //删除 del(x); else if(op==3) //求排名 getrank(x); else if(op==4) //求第rank对应的数 getnum(x); else if(op==5) //求数x的前驱 pre(x); else if(op==6) //求数x的后继 nxt(x); } return 0; }
- 1
信息
- ID
- 164
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 6
- 标签
- 递交数
- 15
- 已通过
- 12
- 上传者