哇最近学平衡树,找资料看模板真的是看的死去活来的。。
花了一上午调了个模板。。
支持以下操作
:
插入x数
删除x数(若有多个相同的数,因只删除一个)
查询x数的排名(若有多个相同的数,因输出最小的排名)
查询排名为x的数
求x的前驱(前驱定义为小于x,且最大的数)
求x的后继(后继定义为大于x,且最小的数)
这个是。。洛谷的模板的描述
模板题:
https://www.luogu.org/problem/show?pid=3369
平衡树可以解决这个问题,这里只介绍treap,即树堆。。(tree+heap。。为什么我觉得这个构词法很不正经)
咳咳。。严肃!
树堆,树:BST(二叉搜索树),堆:堆结构
树堆就是同时保持两者结构的一种数据结构
其中维护BST的关键字是权值,那么堆结构是干什么用的呢?
答案其实是。。防卡
随着时代的发展,毒瘤出题人和毒瘤出数据人越来越多。。
当我们考虑这样一组数据{1,2,3,4,5,6,7,8,…,100000}
。。
我想你们应该明白了些什么
突然爆炸???无fuck说
那么怎么解决这种情况呢?
我们在BST的基础上引入一个新的参数,这里命名为fix,他是随机生成的。
然后我们需要在权值满足BST结构的基础上让fix满足堆结构。小根大根无所谓反正是随机。如果不满足,我们就需要对结构进行调整。
调整过程(zig-zag)等下会说,调整的结果大概是这样的
为什么会这样?
因为如果我们的FIX生成是这样的话
这就是treap的大致思路了
调整过程:
Zig(左旋)——Zag(右旋)
这个。。我先放图吧,口头描述实在太难了
根本目的就是把1和2的位置换一下,,但是由于是二叉树,就要对子节点进行一些变化
并且不难发现,只要除了1和2以外都满足树堆结构,那么旋转后必定也满足树堆结构
inline void up(int k)
{
tr[k].size=tr[tr[k].l].size+tr[tr[k].r].size+tr[k].w;
}
inline void zag(int &k)
{
int t=tr[k].l;
tr[k].l=tr[t].r;
tr[t].r=k;
tr[t].size=tr[k].size;
up(k);
k=t;
}
inline void zig(int &k)
{
int t=tr[k].r;
tr[k].r=tr[t].l;
tr[t].l=k;
tr[t].size=tr[k].size;
up(k);
k=t;
}
分步解析:
1、插入一个数
和BST一样,如果当前节点大于这个要插入的数字,那么往左边继续找,如果小于这个数,则往右边。如果等于。。直接把当前节点的个数+1即可
哇很水啊!
唯一的不同在于我们要加个fix[k]=rand();
并且放好后要满足树堆的性质,往上旋
inline void ins(int &k,int x)
{
if(k==0)
{
k=++tot;
tr[k].w=tr[k].size=1;
tr[k].v=x;
tr[k].fix=rand();
return;
}
tr[k].size++;
if(tr[k].v==x)
{tr[k].w++;return;}
if(tr[k].v>x)
{
ins(tr[k].l,x);
if(tr[tr[k].l].fix<tr[k].fix) zag(k);
}
else
{
ins(tr[k].r,x);
if(tr[tr[k].r].fix<tr[k].fix) zig(k);
}
}
2、删除一个数
。。先通过二叉查找树的性质找到那个点,然后删掉- -
删掉的过程的话,我们注意到treap中是不记录一个点的父亲节点的,无法将它的儿子直接连到父亲上
那怎么办?
我们考虑到无法把儿子接到父亲上,那么如果没有儿子呢?
于是。。往下旋
直到旋不下去,就可以直接删除了
当然如果一个数值有多个,直接减掉一个就可以了,没这么麻烦
inline void del(int &k,int x)
{
if(k==0) return;
if(tr[k].v==x)
{
if(tr[k].w>1)
{
tr[k].w--;tr[k].size--;return;
}
if(tr[k].l==0||tr[k].r==0)
{
k=tr[k].l+tr[k].r;
return;
}
else
if(tr[tr[k].l].fix>tr[tr[k].r].fix)
{
zig(k);
del(k,x);
}
else
{
zag(k);
del(k,x);
}
}
else
{
tr[k].size--;
if(tr[k].v<x)
del(tr[k].r,x);
else
del(tr[k].l,x);
}
}
3、查找一个数字的排名
我们在每一个节点上记录了size,即他的后代总共有多少。
同样通过二叉搜索树的性质,分三类:
1、x在当前节点的左边
2、x就是当前节点
3、x在当前节点的右边
第一类:直接往左搜
第二类:rank=lson.size+1;
第三类:rank=lson.size+x的数量+往右继续搜的结果
inline int get_rank(int k,int x)
{
if(k==0) return 0;
if(tr[k].v==x) return tr[tr[k].l].size+1;
else
if(tr[k].v>x) return get_rank(tr[k].l,x);
else
return tr[tr[k].l].size+tr[k].w+get_rank(tr[k].r,x);
}
4、查找某一排名的数
同样分三类,差不多的写法 所以我就不瞎逼逼了
inline int get_num(int k,int x)
{
if(k==0) return 0;
if(x<=tr[tr[k].l].size) return get_num(tr[k].l,x);
else
if(x>tr[tr[k].l].size+tr[k].w)
return get_num(tr[k].r,x-tr[tr[k].l].size-tr[k].w);
else return tr[k].v;
}
5、查找前驱
一路搜下去,发现比x小的就记录一下,根据二叉搜索树的性质易证,最后一个得到的即最优答案
inline void get_last(int k,int x)
{
if(k==0) return;
if(tr[k].v<x)
{
ans=k;
get_last(tr[k].r,x);
}
else get_last(tr[k].l,x);
}
6、查找后继
同上
inline void get_next(int k,int x)
{
if(k==0) return;
if(tr[k].v<=x)
get_next(tr[k].r,x);
else
{
ans=k;
get_next(tr[k].l,x);
}
}
CODE:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
struct node
{
int l,r,v,w,size,fix;
}tr[100001];
int ans,n,tot,root;
inline void up(int k)
{
tr[k].size=tr[tr[k].l].size+tr[tr[k].r].size+tr[k].w;
}
inline void zag(int &k)
{
int t=tr[k].l;
tr[k].l=tr[t].r;
tr[t].r=k;
tr[t].size=tr[k].size;
up(k);
k=t;
}
inline void zig(int &k)
{
int t=tr[k].r;
tr[k].r=tr[t].l;
tr[t].l=k;
tr[t].size=tr[k].size;
up(k);
k=t;
}
inline void ins(int &k,int x)
{
if(k==0)
{
k=++tot;
tr[k].w=tr[k].size=1;
tr[k].v=x;
tr[k].fix=rand();
return;
}
tr[k].size++;
if(tr[k].v==x)
{tr[k].w++;return;}
if(tr[k].v>x)
{
ins(tr[k].l,x);
if(tr[tr[k].l].fix<tr[k].fix) zag(k);
}
else
{
ins(tr[k].r,x);
if(tr[tr[k].r].fix<tr[k].fix) zig(k);
}
}
inline void del(int &k,int x)
{
if(k==0) return;
if(tr[k].v==x)
{
if(tr[k].w>1)
{
tr[k].w--;tr[k].size--;return;
}
if(tr[k].l==0||tr[k].r==0)
{
k=tr[k].l+tr[k].r;
return;
}
else
if(tr[tr[k].l].fix>tr[tr[k].r].fix)
{
zig(k);
del(k,x);
}
else
{
zag(k);
del(k,x);
}
}
else
{
tr[k].size--;
if(tr[k].v<x)
del(tr[k].r,x);
else
del(tr[k].l,x);
}
}
inline int get_rank(int k,int x)
{
if(k==0) return 0;
if(tr[k].v==x) return tr[tr[k].l].size+1;
else
if(tr[k].v>x) return get_rank(tr[k].l,x);
else return tr[tr[k].l].size+tr[k].w+get_rank(tr[k].r,x);
}
inline int get_num(int k,int x)
{
if(k==0) return 0;
if(x<=tr[tr[k].l].size) return get_num(tr[k].l,x);
else
if(x>tr[tr[k].l].size+tr[k].w)
return get_num(tr[k].r,x-tr[tr[k].l].size-tr[k].w);
else return tr[k].v;
}
inline void get_last(int k,int x)
{
if(k==0) return;
if(tr[k].v<x)
{
ans=k;
get_last(tr[k].r,x);
}
else get_last(tr[k].l,x);
}
inline void get_next(int k,int x)
{
if(k==0) return;
if(tr[k].v<=x)
get_next(tr[k].r,x);
else
{
ans=k;
get_next(tr[k].l,x);
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int kind,x;
scanf("%d%d",&kind,&x);
if(kind==1){ins(root,x);}
if(kind==2){del(root,x);}
if(kind==3){printf("%d\n",get_rank(root,x));}
if(kind==4){printf("%d\n",get_num(root,x));}
if(kind==5){ans=0;get_last(root,x);printf("%d\n",tr[ans].v);}
if(kind==6){ans=0;get_next(root,x);printf("%d\n",tr[ans].v);}
}
}