题意: (裸题还用我说么)
方法: (裸题还用我说么)
解析: 第一次写treap还是费点劲的尤其在理解的时候
首先,定义如下
struct data
{
int l , r , v , rnd , size , w ;
};
data tr[100001] ;
int n , ans , size , root ;
void update(int k)
{
tr[k].size = tr[tr[k].l].size + tr[tr[k].r].size + tr[k].w ;
}
void lturn(int &k)
{
int t = tr[k].r ;
tr[k].r = tr[t].l ;
tr[t].l = k ;
tr[t].size = tr[k].size ;
update(k) ;
k = t ;
}
void rturn(int &k)
{
int t = tr[k].l ;
tr[k].l = tr[t].r ;
tr[t].r = k ;
tr[t].size = tr[k].size ;
update(k) ;
k = t ;
}
左旋右旋只要画两个图,按照图来写就行了,记住6行代码(6句)就不会错。
其次: 插入代码
void insert(int &k , int x)
{
if(k == 0)
{
size ++ ;
k = size ;
tr[k].size = tr[k].w = 1 ;
tr[k].v = x ;
tr[k].rnd = rand() ;
return ;
}
tr[k].size ++ ;
if(tr[k].v == x) tr[k].w ++ ;
else if(x > tr[k].v)
{
insert(tr[k].r , x) ;
if(tr[tr[k].r].rnd < tr[k].rnd) lturn(k) ;
}else
{
insert(tr[k].l , x) ;
if(tr[tr[k].l].rnd < tr[k].rnd) rturn(k) ;
}
}
1.先讨论没有节点的情况
2.再讨论所插节点恰好在k上
3.再讨论所插节点的值与k处的值的大小关系,若大于则插到右子树,反之插到左子树。同时要维护小根堆的特性。
删除函数
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 * tr[k].r == 0) k = tr[k].l + tr[k].r ;
else if(tr[tr[k].l].rnd < tr[tr[k].r].rnd)
{
rturn(k) ;
del(k , x) ;
}else
{
lturn(k) ;
del(k , x) ;
}
}else if(x > tr[k].v)
{
tr[k].size -- ;
del(tr[k].r , x) ;
}else
{
tr[k].size -- ;
del(tr[k].l , x) ;
}
}
1.讨论没有节点不删除
2.如果恰好在k处
1.k处的数多于1个则直接删除
2.k处只有一个子节点,则该节点替换k
3.如果左边rnd小于右边rnd,右旋,把这个根节点旋到叶节点处删掉
4.与(3)相反则左旋到叶节点删除
3.如果比k处的值大则k的size--,再分到右子树删除。
4.如果比k处的值小则k的size--,再分到左子树删除。
查询x的排名
int query_rank(int k , int x)
{
if(k == 0) return 0 ;
if(tr[k].v == x) return tr[tr[k].l].size + 1 ;
else if(x > tr[k].v)
{
return tr[tr[k].l].size + tr[k].w + query_rank(tr[k].r , x) ;
}else return query_rank(tr[k].l , x) ;
}
1.讨论没有节点的情况
2.如果恰好就是在k处则直接返回左子树的节点数加1
3.如果比k处的值大则返回左子树的节点数加k处点数加对右子树的递归询问
4.如果比k处的值小则返回左子树的递归询问
查询排名为x的数
int query_num(int k , int x)
{
if(k == 0) return 0 ;
if(x <= tr[tr[k].l].size)
{
return query_num(tr[k].l , x) ;
}else if(x > tr[tr[k].l].size + tr[k].w)
{
return query_num(tr[k].r , x - tr[tr[k].l].size - tr[k].w) ;
}else
{
return tr[k].v ;
}
}
1.讨论没有节点的情况
2.如果x小于等于左子树的节点数返回左子树的递归询问
3.如果x比左子树的节点数加k处节点数还大返回对右子树的 x-左子树节点数-k处节点数 的递归询问
4.除了2.3.的情况则一定是k处的数,返回k处的值
求前驱
void query_pro(int k , int x)
{
if(k == 0) return ;
if(tr[k].v < x)
{
ans = k ;
query_pro(tr[k].r , x) ;
}else query_pro(tr[k].l , x) ;
}
1.讨论没有节点的情况
2.如果x比k处的值大则用ans记录k,再二分到右子树
3.与2相反则二分到左子树
求后继
void query_sub(int k , int x)
{
if(k == 0) return ;
if(tr[k].v > x)
{
ans = k ;
query_sub(tr[k].l , x) ;
}else query_sub(tr[k].r , x) ;
}
1.讨论没有节点的情况
2.如果x比k处的值小则用ans记录k再二分到左子树
3.与2相反则二分到右子树
主函数(这货完全就是来凑字数的)
int main()
{
scanf("%d" , &n) ;
int opt , x ;
for(int i = 1 ; i <= n ; i++)
{
scanf("%d%d" , &opt , &x) ;
switch(opt)
{
case 1 :insert(root , x) ;break ;
case 2 :del(root , x) ;break ;
case 3 :printf("%d\n" , query_rank(root , x)) ;break ;
case 4 :printf("%d\n" , query_num(root , x)) ;break ;
case 5 :ans = 0;query_pro(root , x);printf("%d\n" , tr[ans].v);break ;
case 6 :ans = 0;query_sub(root , x);printf("%d\n" , tr[ans].v);break ;
}
}
}