网址:https://www.luogu.org/problem/P3369
题意:
编写一个数据结构在每次$O(logn)(1 \leq n \leq 1e6)$完成以下功能:
一、插入一个数到序列中;二、在序列中删除某一个数;三、找到第$k$大;四、询问第$k$大的数;五、找到$x$的前驱,六、找到$x$的后继。
题解:
很显然,二叉搜索树就可以完成这个任务,不过最坏情况下,二叉树会退化成链,就会超时,因此我们就需要平衡二叉树,其中较为容易编写,速度又快的是treap。treap分为有旋treap和无旋treap,前者速度快,但不支持持久化,后者速度慢,但支持持久化。
一、有旋treap
treap的节点的权值维护了二叉树的性质,为了平衡,就需要通过另外一个数维护堆性质使其平衡,这个数就是每一个节点对应的随机数。旋转的时候按照随机数进行旋转,分为左旋和右旋,如图:
(参考博客:https://blog.csdn.net/K346K346/article/details/50808879)
左旋和右旋
显然,右旋就是原树根的左子树变成树根,然后原树根的左子树的右子树变成原树根的左子树,左旋同理。易证旋转后二叉树性质不变。
插入时,先找到插入点,然后回溯时旋转。
删除较为复杂,删除时,先找到删除点,然后观察子树的随机数值选择一个子树作为删除后的树根,然后通过旋转把需要删除的节点移动到叶子后删除。
查询操作易于理解,看代码即可。
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int MAXN=100005;
const int inf=0x3f3f3f3f;
struct Treap
{
struct node
{
int val, rnd, lc, rc, size, num;
};
int cnt=0;
node tr[MAXN];
void init()
{
cnt=0;
}
int _rand()
{
static int seed=12345;
return seed=(int)seed*482711LL%2147483647;
}
void pushup(int p)
{
tr[p].size=tr[tr[p].lc].size+tr[tr[p].rc].size+tr[p].num;
}
void right(int &k)
{
int tmp=tr[k].lc;
tr[k].lc=tr[tmp].rc;
tr[tmp].rc=k;
tr[tmp].size=tr[k].size;
pushup(k);
k=tmp;
}
void left(int &k)
{
int tmp=tr[k].rc;
tr[k].rc=tr[tmp].lc;
tr[tmp].lc=k;
tr[tmp].size=tr[k].size;
pushup(k);
k=tmp;
}
void insert(int &p,int x)
{
if(p==0)
{
p=++cnt;
tr[p].val=x;
tr[p].num=tr[p].size=1;
tr[p].lc=tr[p].rc=0;
tr[p].rnd=_rand();
return;
}
++tr[p].size;
if(x==tr[p].val)
++tr[p].num;
else if(x<tr[p].val)
{
insert(tr[p].lc,x);
if(tr[tr[p].lc].rnd<tr[p].rnd)
right(p);
}
else if(x>tr[p].val)
{
insert(tr[p].rc,x);
if(tr[tr[p].rc].rnd<tr[p].rnd)
left(p);
}
}
void del(int &p,int x)
{
if(p==0)
return;
if(tr[p].val==x)
{
if(tr[p].num>1)
--tr[p].num,--tr[p].size;
else
{
if(tr[p].lc==0||tr[p].rc==0)
p=tr[p].lc+tr[p].rc;
else if(tr[tr[p].lc].rnd<tr[tr[p].rc].rnd)
right(p),del(p,x);
else if(tr[tr[p].lc].rnd>tr[tr[p].rc].rnd)
left(p),del(p,x);
}
}
else if(tr[p].val<x)
--tr[p].size,del(tr[p].rc,x);
else
--tr[p].size,del(tr[p].lc,x);
}
int queryrnk(int &p,int x)
{
if(p==0)
return 0;
else if(tr[p].val==x)
return tr[tr[p].lc].size+1;
else if(tr[p].val<x)
return tr[tr[p].lc].size+tr[p].num+queryrnk(tr[p].rc,x);
else
return queryrnk(tr[p].lc,x);
}
int querynum(int &p,int rnk)
{
if(p==0)
return 0;
if(tr[tr[p].lc].size>=rnk)
return querynum(tr[p].lc,rnk);
rnk-=tr[tr[p].lc].size;
if(rnk<=tr[p].num)
return tr[p].val;
rnk-=tr[p].num;
return querynum(tr[p].rc,rnk);
}
int queryfront(int &p,int x)
{
if(p==0)
return -inf;
if(tr[p].val<x)
return max(tr[p].val,queryfront(tr[p].rc,x));
else if(tr[p].val>=x)
return queryfront(tr[p].lc,x);
}
int queryback(int &p,int x)
{
if(p==0)
return inf;
if(tr[p].val>x)
return min(tr[p].val,queryback(tr[p].lc,x));
else if(tr[p].val<=x)
return queryback(tr[p].rc,x);
}
};
int pos;
Treap tr;
int main()
{
int n;
scanf("%d",&n);
int m,k;
tr.init();
for(int i=0;i<n;++i)
{
scanf("%d%d",&m,&k);
if(m==1)
tr.insert(pos,k);
else if(m==2)
tr.del(pos,k);
else if(m==3)
printf("%d\n",tr.queryrnk(pos,k));
else if(m==4)
printf("%d\n",tr.querynum(pos,k));
else if(m==5)
printf("%d\n",tr.queryfront(pos,k));
else if(m==6)
printf("%d\n",tr.queryback(pos,k));
}
return 0;
}