平衡二叉树之treap,手动随机,几次旋转,p1999与p1997

  (转自澜神PPT) 

  普通的BST会卡,主要是由于仰仗于随机数据,如果"用心"构造数据就会被卡掉.考虑如何不被卡掉呢?我们可以"随机构建二叉树.算法导论第三版第169页-第171页有关于"一课有n个不同关键字的随机构建二叉搜索树的期望高度为lg(n)"的具体证明,有兴趣的可以看一看..所以对于离线的题,可以先随机排列这些元素,然后按照排列的顺序将他们插入树中.

  如果没法同时得到所有元素,应该怎样处理呢?

  treap是一课更改了节点排序方式的BST,除了键值v外,每个节点都指定一个随机数作为优先级.假设所有的优先级都不同,一个合法的treap树上键值满足BST的性质,优先级遵循堆性质.可以证明对于特定的有键值和优先级的一些数有唯一的treap与之对应.可以证明所有的操作期望运行时间是log(n).可以证明每次插入新节点时执行旋转的期望次数小于2..

  treap的具体实现怎么做呢?这里用一个例题做介绍.

  每个测试点0.2s还是丧心病狂啊.摆明了要卡快读(╯‵□′)╯︵┻━┻.也不知道普通BST能不能A.也可以发现通过率极低.

  如果看了上一篇博客来直接写应该还行吧...插入删除,求前驱求后继都可写.考虑如何用键值求排名和用排名求键值?如果看了当年主席树第k大的朋友应该也能应付:维护子树的大小,如果在左子树就拐到左子树,否则拐到右子树这样子递归查询即可.但是我们想用treap写这道题,如何实现呢?

  

 getrank()和getv()大致和主席树求第k大相似:

  为了同时维护键值的二分查找树的性质与优先级的堆性质,我们引入左旋与右旋的概念.

  给出左右旋的作用:调用右旋zig(x)时,把x的左儿子提为自己的父亲,x做它的右儿子.同时接管它的右儿子作为自己的左儿子.左旋时把x的右儿子提为自己的父亲,做它的左儿子.同时接管左儿子作为自己的右儿子.

  这样做一下之后,BST的性质没有改变.对于堆性质来说,只有2,4的上下相对位置改变.可以用来维护堆性质.

  然后就可以开心的进行插入操作了.每次对于一个v,rand一个随机数作为优先级,先插入,如果优先级有问题就转一下~

  然后就又来到了删除的环节...这回我们可以利用旋转,找到后直接向下旋转直到把它弄到树的叶子层,然后用&now把自己弄成0,它的父亲记录自己位置的l或r也为0了,就相当于已经被扔掉了每次都觉得好悲伤.具体实现能否用get下标()函数呢?考虑到删除后还要更新祖先的size,不如写递归函数.好像用while也可以写,主要是因为懒. 

  求前驱后继什么的都与之前类似了,也不涉及树的更新.这里不给代码.

struct node
{
    int l,r;
    int v,dat;
    int cnt,size;
}a[100010];
int tot,root,n,INF=1<<30;
inline int New(int v)
{
    a[++tot].v=v;
    a[tot].dat=rand();
    a[tot].cnt=a[tot].size=1;
    return tot;
}
inline void Update(int now)
{
    a[now].size=a[a[now].l].size+a[a[now].r].size+a[now].cnt;
    return ;
}
void Build()
{
    New(-INF),New(INF);
    root=1;a[1].r=2;
    Update(root);
    return ;
}
int Getrank(int now,int v)
{
    if(now==0)return 0;
    if(v==a[now].v) return a[a[now].l].size+1;
    if(v<a[now].v)return Getrank(a[now].l,v);
    return Getrank(a[now].r,v)+a[a[now].l].size+a[now].cnt;
}
int Getv(int now,int rank)
{
    if (now==0)return INF;
    if(a[a[now].l].size>=rank)return Getv(a[now].l,rank);
    if(a[a[now].l].size+a[now].cnt>=rank)return a[now].v;
    return Getv(a[now].r,rank-a[a[now].l].size-a[now].cnt);
}
void Zig(int &now)
{
    int q=a[now].l;
    a[now].l=a[q].r,a[q].r=now,now=q;
    Update(a[now].r),Update(now);
    return ;
}
void Zag(int &now)
{
    int q=a[now].r;
    a[now].r=a[q].l,a[q].l=now,now=q;
    Update(a[now].l),Update(now);
    return ;
}
void Insert(int &now,int v)
{
    if(now==0)
    {
        now=New(v);
        return ;
    }
    if(v==a[now].v)
    {
        a[now].cnt++;Update(now);
        return ;
    }
    if(v<a[now].v)
    {
        Insert(a[now].l,v);
        if(a[now].dat<a[a[now].l].dat)Zig(now);
    }
    else
    {
        Insert(a[now].r,v);
        if(a[now].dat<a[a[now].r].dat)Zag(now);
    }
    Update(now);
    return ;
}
int Getpre(int v)
{
    int ans=1;int now=root;
    while(now)
    {
        if(v==a[now].v)
        {
            if(a[now].l>0)
            {
                now=a[now].l;
                while(a[now].r>0)now=a[now].r;
                ans=now;
            }
            break;
        }
        if(a[now].v<v&&a[now].v>a[ans].v)ans=now;
        now=v<a[now].v?a[now].l:a[now].r;
    }
    return a[ans].v;
}
int Getnext(int v)
{
    int ans=2,now=root;
    while(now)
    {
        if(v==a[now].v)
        {
            if(a[now].r>0)
            {
                now=a[now].r;
                while(a[now].l>0)
                    now=a[now].l;
                ans=now;
            }
            break;
        }
        if(a[now].v>v&&a[now].v<a[ans].v)ans=now;
        now=v<a[now].v?a[now].l:a[now].r;
    }
    return a[ans].v;
}
void Remove(int &now,int v)
{
    if(now==0)
        return ;
    if(v==a[now].v)
    {
        if(a[now].cnt>1)
        {
            a[now].cnt--,Update(now);
            return ;
        }
        if(a[now].l||a[now].r)
        {
            if(a[now].r==0||a[a[now].l].dat>a[a[now].r].dat)
                Zig(now),Remove(a[now].r,v);
            else
                Zag(now),Remove(a[now].l,v);
            Update(now);
        }
        else
            now=0;
        return ;
    }
    v<a[now].v?Remove(a[now].l,v):Remove(a[now].r,v);
    Update(now);
    return ;
}
int topt,tx;
int main()
{
//freopen("123.in","r",stdin);
    Build();
    for(n=read();n;n--)
    {
        topt=read();tx=read();
        switch(topt)
        {
            case 1:
                Insert(root,tx);
                break;
            case 2:
                Remove(root,tx);
                break;
            case 3:
                write(Getrank(root,tx)-1);
                break;
            case 4:
                write(Getv(root,tx+1));
                break;
            case 5:
                write(Getpre(tx));
                break;
            case 6:
                write(Getnext(tx));
                break;
        }
    }
    return 0;
}
P1999

 

  再来看一道题~

  看一眼操作,好像都很熟悉但是又都不会.好像可以上主席树.但是这里强行让你上平衡树该怎么做呢?假如所有询问更改区间都是[1,n]你会不会呢?正是上一题.考虑如何做这道题呢?我们需要线段树套平衡树

  

先开一个线段树,每个区间都是一个treap.
建树的时候枚举所有的数一个个插入线段树中.而且真正存线段树的数组是没有的,因为它只维护该区间的treap的根节点而已.
对于操作1,k的排名等价于小于k的数+1,可以用线段树在整块的区间内求出小于k的个数求和后输出+1,复杂度期望log(n)^2.
对于操作2,我们可以利用操作1进行二分求解.即二分答案x,每个x都log(n)^2求解区间内的排名,如果离散化是可以做到log(n)^3.(当时没想到离散化,log(n)变log(1e8)).
对于操作3,可以删除后insert,和相当于普通平衡树操作1+操作2.
对于操作4,求前驱显然可以在区间内求出前驱并取max.
对于操作5,求后继可以在区间内求出后继并取min.

   我的代码常数很大,但是n,m都不太大,评测机还开了2s,所以才A掉.

  

  所以说树套树并没有那么恐怖了,不就是两百多行代码么.

  

#define inf 2000000007
using namespace std;
int n,m,ans,tot;
struct node{
    int l,r;
    int dat,v;
    int size,cnt;
}o[4000010];
int root[200010],a[200010];
void Update(int now){
    o[now].size=o[o[now].l].size+o[o[now].r].size+o[now].cnt;
    return;
}
void Zig(int &now){
    int p=o[now].l;
    o[now].l=o[p].r;
    o[p].r=now;
    o[p].size=o[now].size;
    Update(now);
    now=p;
    return;
}
void Zag(int &now){
    int p=o[now].r;
    o[now].r=o[p].l;
    o[p].l=now;
    o[p].size=o[now].size;
    Update(now);
    now=p;
    return;
}
void Insert(int &now,int v){
    if(!now){
        tot++;
        now=tot;
        o[now].size=o[now].cnt=1;
        o[now].v=v;
        o[now].dat=rand();
        return;
    }
    o[now].size++;
    if(v==o[now].v){
        o[now].cnt++;
        return ;
    }
    if(v<o[now].v){
        Insert(o[now].l,v);
        if (o[o[now].l].dat<o[now].dat)
            Zig(now);
    }
    else{
        Insert(o[now].r,v);
        if(o[o[now].r].dat<o[now].dat)
            Zag(now); 
    }
    return;
}
void del(int &now,int x){
    if(now==0) return;
    if(o[now].v==x){
        if(o[now].cnt>1) o[now].cnt--,o[now].size--;
        else{
            if(o[now].l==0||o[now].r==0) now=o[now].l+o[now].r;
            else if(o[o[now].l].dat<o[o[now].r].dat) Zig(now),del(now,x);
            else Zag(now),del(now,x);
        }
        return ;
    }
    x>o[now].v?del(o[now].r,x):del(o[now].l,x);
    Update(now);
    return ;
}
void add(int now,int l,int r,int x,int v)
{
    Insert(root[now],v);//线段树内只需要存root
    //在 以root[now]为树根的树中加入v
    if (l==r)
        return;
    int mid=(l+r)>>1;now=now<<1;
    if(x<=mid)
        add(now,l,mid,x,v);
    else 
        add(now+1,mid+1,r,x,v);
    return;
}
void rankplus(int now,int v){
    if (!now)return;
    if (v==o[now].v)ans+=o[o[now].l].size;
    else if(v<o[now].v)rankplus(o[now].l,v);
    else{
        ans+=o[o[now].l].size+o[now].cnt;
        rankplus(o[now].r,v); 
    }
    return;
}
void getrank(int now,int l,int r,int x,int y,int v){
    if(l==x&&r==y){//对于整块的
        rankplus(root[now],v);
        return;
    }
    int mid=(l+r)>>1;now=now<<1;
    if (y<=mid) getrank(now,l,mid,x,y,v);
    else if (x>mid) getrank(now|1,mid+1,r,x,y,v);
    else getrank(now,l,mid,x,mid,v),getrank(now|1,mid+1,r,mid+1,y,v);
    return;
}
void getv(int x,int y,int v){
    //二分答案!
    int l=0,r=1e8,res;
    while(l<=r){
        int mid=(l+r)>>1;
        ans=1;
        getrank(1,1,n,x,y,mid);
        if(ans<=v){
            l=mid+1;
            res=mid;
        }
        else r=mid-1;
    }
    write(res);    
    return;

}
void modify(int now,int l,int r,int x,int yl,int xz){
    del(root[now],yl);
    Insert(root[now],xz);
    if (l==r) return;
    int mid=(l+r)>>1;
    if (x<=mid)modify(now<<1,l,mid,x,yl,xz);
    else modify(now<<1|1,mid+1,r,x,yl,xz);
    return;
}
void qianqu(int now,int v){
    if (!now) return;
    if (o[now].v<v){
        ans=max(ans,o[now].v);
        qianqu(o[now].r,v);
    }
    else qianqu(o[now].l,v);
    return;
}
void getbefore(int now,int l,int r,int x,int y,int v)
{
    if (l==x&&r==y){
        qianqu(root[now],v);
        return;
    }
    int mid=(l+r)>>1;now=now<<1;
    if (y<=mid) getbefore(now,l,mid,x,y,v);
    else if (x>mid) getbefore(now+1,mid+1,r,x,y,v);
    else getbefore(now,l,mid,x,mid,v),getbefore(now+1,mid+1,r,mid+1,y,v);
    return;
}
void houji(int now,int v)
{
    if(!now)return;
    if(o[now].v>v){
        ans=min(o[now].v,ans);
        houji(o[now].l,v);
    }
    else houji(o[now].r,v);
    return;
}
void getafter(int now,int l,int r,int x,int y,int v)
{
    if (l==x&&r==y){
        houji(root[now],v);
        return;
    }
    int mid=(l+r)>>1;
    if (y<=mid) getafter(now<<1,l,mid,x,y,v);
    else if (x>mid) getafter(now<<1|1,mid+1,r,x,y,v);
    else getafter(now<<1,l,mid,x,mid,v),getafter(now<<1|1,mid+1,r,mid+1,y,v);
    return;
}
int main(){
    n=read(),m=read();
    for (int i=1;i<=n;i++)
        a[i]=read(),add(1,1,n,i,a[i]);//在第i个位置加入a[i]
    while(m--)   {
        int flag=read(),x,y,k;
        switch(flag)     {
            case 1:x=read(),y=read(),k=read(),ans=1,getrank(1,1,n,x,y,k),write(ans);break;
            case 2:x=read(),y=read(),k=read(),getv(x,y,k);break;
            case 3:x=read(),y=read(),modify(1,1,n,x,a[x],y),a[x]=y;break;
            case 4:x=read(),y=read(),k=read(),ans=0,getbefore(1,1,n,x,y,k),write(ans);break;
            case 5:x=read(),y=read(),k=read(),ans=inf,getafter(1,1,n,x,y,k),write(ans);break;
        }
    }
}
p1997

   说起来,真的有人可以在考场上写出来树套树么...恐怖如斯

posted @ 2019-01-18 08:53  zzuqy  阅读(250)  评论(0编辑  收藏  举报