平衡树学习笔记——旋转式treap

哇最近学平衡树,找资料看模板真的是看的死去活来的。。
花了一上午调了个模板。。
支持以下操作

插入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);}
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值