浅谈treap

我写博客是给自己看的,所以别喷我丑

treap是一种特别神奇的东西,从英文字母中可以看出其的本质:tree+heap

tree——BST二叉搜索树,heap——堆

首先二叉搜索树就不讲了(一句话:满足左子树所有结点的值<根结点的值<右子树所有结点的值)

在最优的情况下,是O(n lg n),这是非常非常优秀的时间复杂度

但是如果单单维护二叉搜索树,当它是一条链的时候时间复杂度是O(n^2),这与暴力是一样的

所以无良的出题人甚至会把你卡的怀疑人生

 

这个时候,你会怎么想?

一条链的树可以通过变换形态在本质不变(中序遍历不变)的情况下变成一棵平衡二叉树

如果这时候能把子树的根换掉不出现一条链的情况就好了

 

那该怎么办呢?

脑洞极大的人想出了一种方法,再给每个点随机一个值,然后用堆的形式维护那个值,

这样一条链的可能就差不多和彩票中奖一样。

 

这样思想就讲完了,是不是很简单啊?

如何维护呢?换根,跟splay一样,菜鸡先朦朦胧胧地学了splay的思想至今还没写过

维护的时候就设计到一个技巧:zig/zag(左旋/右旋)

inline void zig(int &x,int y)
{
    lc[x]=rc[y],rc[y]=x;
    siz[y]=siz[x],siz[x]=siz[lc[x]]+siz[rc[x]]+c[x];
    x=y;
}  //lc表示左儿子,rc表示右儿子,siz表示内部结点数,c表示相同权值的数的个数(因为普通平衡树中有重复),x为原来的根,y为后来的根

inline void zag(int &x,int y)
{
    rc[x]=lc[y],lc[y]=x;
    siz[y]=siz[x],siz[x]=siz[lc[x]]+siz[rc[x]]+c[x];
    x=y;
}

例题(唯一的例题): Luogu P3369 【模板】普通平衡树

https://www.luogu.org/problemnew/show/P3369

这题涉及了六个基本操作:

     1、插入  首先按照BST里的插入方法找到合法的位置插入,在随机该结点在堆上的值,然后换根维护堆的性质就好了

                 

void ins(int &x,int k)
    {
        if(!x) 
        {
            x=++cnt,tre[x]=k,hea[x]=rand()%2000000000,siz[x]=1,c[x]=1;
            return;
        }
        siz[x]++;
        if(tre[x]==k) c[x]++;
        else if(tre[x]<k) 
        {
            ins(rc[x],k);  
            if(hea[rc[x]]<hea[x]) zag(x,rc[x]);
        }else
        {
            ins(lc[x],k);
            if(hea[lc[x]]<hea[x]) zig(x,lc[x]);
        }
    }  //tre是tree的缩写,是BST意义上的值,hea是heap的缩写,是堆意义上的值,怎么说怎么做即可

 

       2、删除:找到删除的结点,如果有多个可能解,就删掉1个(定义);如果它只有1个儿子,直接强行链接儿子和父亲;如果在叶子结点,直接删掉。

     但是都不是呢?

     强行把结点转到可以按以上操作处理的位置进行操作

     

 void del(int &x,int k)
    {
        if(tre[x]==k)
        {
            if(c[x]>1) c[x]--,siz[x]--;
                else if(!lc[x]||!rc[x]) x=lc[x]+rc[x];
                    else if(hea[lc[x]]<hea[rc[x]]) zig(x,lc[x]),del(x,k); 
                        else zag(x,rc[x]),del(x,k); //一定要是x,不然会MLE
            return;
        }
        siz[x]--;
        if(k<tre[x]) del(lc[x],k);
            else del(rc[x],k);
    }

           3、查询x数的排名:这和二叉搜索树一样,比较它与目标结点,如果小了就遍历左子树,如果大了就遍历右子树,否则返回

int find(int x,int k)
    {
        if(tre[x]==k) return siz[lc[x]]+1;
        if(tre[x]>k) return find(lc[x],k);
        return c[x]+siz[lc[x]]+find(rc[x],k);		
    }

        4、查找第K大的数,也和二叉搜索树一样,略

 int kth(int x,int k)
    {
        if(siz[lc[x]]<k&&k<=siz[lc[x]]+c[x]) return tre[x];
        if(siz[lc[x]]>=k) return kth(lc[x],k);
        return kth(rc[x],k-siz[lc[x]]-c[x]);
    }

      5、找前驱:目标结点大于等于查询的数,就遍历左子树,否则可能是自己或者其右子树中某个结点

  int pre(int x,int k)
    {
        if(!x) return -1e9;   //注意一定要足够小
        if(tre[x]>=k) return pre(lc[x],k);
        return max(pre(rc[x],k),tre[x]);
    } 

      6、找后驱:同上,略

  int nxt(int x,int k)
    {
        if(!x) return 1e9;
        if(tre[x]<=k) return nxt(rc[x],k);
        return min(nxt(lc[x],k),tre[x]);
    }

 

以上就是菜鸡写的treap,放一个完整的代码

#include<cstdio>
#include<iostream>
#include<ctime>
#include<algorithm>
using namespace std;

const int N=1e6+5;
int n,r;

struct A
{
    int cnt,r,lc[N],rc[N],tre[N],hea[N],siz[N],c[N];
    
    inline void zig(int &x,int y)
    {
        lc[x]=rc[y],rc[y]=x;
        siz[y]=siz[x],siz[x]=siz[lc[x]]+siz[rc[x]]+c[x];
        x=y;
    }
    
    inline void zag(int &x,int y)
    {
        rc[x]=lc[y],lc[y]=x;
        siz[y]=siz[x],siz[x]=siz[lc[x]]+siz[rc[x]]+c[x];
        x=y;
    }
    
    void ins(int &x,int k)
    {
        if(!x) 
        {
            x=++cnt,tre[x]=k,hea[x]=rand()%2000000000,siz[x]=1,c[x]=1;
            return;
        }
        siz[x]++;
        if(tre[x]==k) c[x]++;
        else if(tre[x]<k) 
        {
            ins(rc[x],k);  
            if(hea[rc[x]]<hea[x]) zag(x,rc[x]);
        }else
        {
            ins(lc[x],k);
            if(hea[lc[x]]<hea[x]) zig(x,lc[x]);
        }
    }
    
    void del(int &x,int k)
    {
        if(tre[x]==k)
        {
            if(c[x]>1) c[x]--,siz[x]--;
                else if(!lc[x]||!rc[x]) x=lc[x]+rc[x];
                    else if(hea[lc[x]]<hea[rc[x]]) zig(x,lc[x]),del(x,k);
                        else zag(x,rc[x]),del(x,k);
            return;
        }
        siz[x]--;
        if(k<tre[x]) del(lc[x],k);
            else del(rc[x],k);
    }
    
    int find(int x,int k)
    {
        if(tre[x]==k) return siz[lc[x]]+1;
        if(tre[x]>k) return find(lc[x],k);
        return c[x]+siz[lc[x]]+find(rc[x],k);		
    }
    
    int kth(int x,int k)
    {
        if(siz[lc[x]]<k&&k<=siz[lc[x]]+c[x]) return tre[x];
        if(siz[lc[x]]>=k) return kth(lc[x],k);
        return kth(rc[x],k-siz[lc[x]]-c[x]);
    }
    
    int pre(int x,int k)
    {
        if(!x) return -1e9;
        if(tre[x]>=k) return pre(lc[x],k);
        return max(pre(rc[x],k),tre[x]);
    } 
    
    int nxt(int x,int k)
    {
        if(!x) return 1e9;
        if(tre[x]<=k) return nxt(rc[x],k);
        return min(nxt(lc[x],k),tre[x]);
    }
}treap;

int read()
{
    int ret=0; bool f=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') 
    {
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
        ret=(ret<<1)+(ret<<3)+ch-'0',
        ch=getchar();
    return f?-ret:ret;
}

void write(int x)
{
    if(x<0) 
    {
        putchar('-'),write(-x);
        return;
    }
    if(x/10) write(x/10);
    putchar(x%10+48);
}

int main()
{
    srand(time(NULL));
    n=read();
    while(n--)
    {
        int t=read(),x=read(); 
        if(t==1) treap.ins(treap.r,x);
        else if(t==2) treap.del(treap.r,x);
        else if(t==3) write(treap.find(treap.r,x)),puts("");
        else if(t==4) write(treap.kth(treap.r,x)),puts("");
        else if(t==5) write(treap.pre(treap.r,x)),puts("");
        else if(t==6) write(treap.nxt(treap.r,x)),puts("");
    }
    return 0;
}

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值