洛谷【模板】普通平衡树

题面

splay 不停旋转保持树高,比treap还是要快的

#define rg register
#define _ (int)1e5+1
using namespace std;
int n,m,root,num;
struct pp
{
    int son[2],dad,it,cnt,size; //cnt:这个点有多少个值;size:子树上有多少个值
}tr[_];//好像空间是O(n)吧?
inline int read()
{
    rg int save=0,w=1;rg char q=getchar(); //!!!!!! =getsonar()  不可少!!!! (否则q可能会有初值)
    while(q<'0'||q>'9'){if(q=='-')w=-1;q=getchar();}
    while(q>='0'&&q<='9')save=(save<<3)+(save<<1)+q-'0',q=getchar();
    return  w*save;
}
inline void pushup(rg int x)
{
    tr[x].size=tr[tr[x].son[0]].size+tr[tr[x].son[1]].size+tr[x].cnt;//加了两个儿子别忘了加自己
}
inline void doit(rg int x)//一个结点的旋转
{
    rg int y=tr[x].dad,z=tr[y].dad;
    rg bool k=tr[y].son[1]==x;
    tr[x].dad=z;
    tr[z].son[tr[z].son[1]==y]=x;
    tr[y].son[k]=tr[x].son[!k];
    tr[tr[y].son[k]].dad=y;
    tr[y].dad=x;
    tr[x].son[!k]=y;
    pushup(y),pushup(x);//记住要更新size!!!
}
inline void splay(rg int x,rg int to)//把某节点旋转多次(或一次)成to的儿子
{
    while(tr[x].dad!=to)
    {
        rg int y=tr[x].dad,z=tr[y].dad;
        //由于x与y在其父亲的同側时,如果只是一路把x转上去的话,有一条链始终没被拆,保证不了时间复杂度,所以我们这样做:
        if(z!=to)  //
            ((tr[z].son[1]==y)^(tr[y].son[1]==x))?doit(x):doit(y);//在其父亲同側就转y,不在就没关系可以转x
        doit(x);
    }
    if(!to)root=x;              //记得更新根节点!!!
}
inline void find(rg int x)//值为x的点
{
    rg int now=root;
    if(!now)return;//看情况加不加此句
    while(tr[now].it!=x&&tr[now].son[x>tr[now].it])
        now=tr[now].son[x>tr[now].it];
    splay(now,0);
}
inline void insert(rg int x)
{
    rg int now=root,fa=0;
    while(now&&tr[now].it!=x)     //先从根节点往下找到第一个 值与x相等 或 不存在 的点
        fa=now,now=tr[now].son[x>tr[now].it]; //1: right son, 2: left son
    if(tr[now].it==x)
        tr[now].cnt++;               //
    else
    {
        now=++num;
        tr[now].it=x,tr[now].cnt=1;  //
        tr[now].dad=fa;
        tr[now].size=1;
        if(fa)
            tr[fa].son[x>tr[fa].it]=now;
//        tr[now].size++;              //attention!!!反正在下一行的splay()中会进入pushup()函数而计算子树大小,所以无需提前算size
    }
    splay(now,0);
}
inline int Kth(rg int x)
{
    rg int now=root;
    if(x>tr[root].size)return 0;
    while(666)
    {
        rg int front=tr[tr[now].son[0]].size+tr[now].cnt;
        if(x<=front&&x>tr[tr[now].son[0]].size)return tr[now].it;
        if(x<=tr[tr[now].son[0]].size)
            now=tr[now].son[0];
        else now=tr[now].son[1],x-=front;
/*            if(x>front)now=tr[now].son[1],x-=front;
            else return tr[now].it;
*/    }
}
inline int next(rg int x,rg bool b)
{
    find(x);
    rg int now=root;
    if(x<tr[now].it&&b)return now;//返回的是指针啊喂
    if(x>tr[now].it&&(!b))return now;
    now=tr[now].son[b];
    while(tr[now].son[!b])now=tr[now].son[!b];
    return now;
}
inline void cut(rg int x)
{
    rg int l=next(x,0);
    rg int r=next(x,1);
    splay(l,0);
    splay(r,l);
    rg int now=tr[r].son[0];
    if(tr[now].cnt>1)
    {
        tr[now].cnt--;
        splay(now,0);
    }
    else
    {
        if(tr[r].son[0]==num)--num;
        tr[r].son[0]=0;
//        pushup(r),pushup(root);
        //不用上上行的splay操作,但记得要pushup,不然假如下一个操作是求第k大,size没更新就可能错啦
        //但其实也不用pushup(你已经把前驱转到了根节点,这个点在后继的左儿子,求第k大的时候会考虑到它的size)
    }
}
int main()
{
    n=read();
    rg int i,j;
//    for(i=1;i<=n;++i)tr[i].it=i,tr[i].son[1]=i+1,tr[i].dad=i-1;错误建树示范(必须不停旋转让树保持log(n)的树高,这样建条链不行)
    insert(2147483647),insert(-2147483647);//给树最两端的点以前驱和后继,方便删除它们
    for(i=1;i<=n;++i)
    {
        rg int op=read(),x=read();
        if(op==1)
            insert(x);
        if(op==2)
            cut(x);
        if(op==3)
            find(x),printf("%d\n",tr[tr[root].son[0]].size);
        if(op==4)
            printf("%d\n",Kth(x+1));
        if(op==5)
            printf("%d\n",tr[next(x,0)].it);
        if(op==6)
            printf("%d\n",tr[next(x,1)].it);
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/c-wen/p/9162331.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值