题意: (裸题还用我说么)
方法: (裸题还用我说么)
解析: 第一次写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 ; } } }