[bzoj4825][HNOI2017]单旋

题目描述

H 国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构。伸展树(splay)是一种数据
结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了 H 国的必修技能。有一天,邪恶的“卡”带着
他的邪恶的“常数”来企图毁灭 H 国。“卡”给 H 国的人洗脑说,splay 如果写成单旋的,将会更快。“卡”称
“单旋 splay”为“spaly”。虽说他说的很没道理,但还是有 H 国的人相信了,小 H 就是其中之一,spaly 马
上成为他的信仰。 而 H 国的国王,自然不允许这样的风气蔓延,国王构造了一组数据,数据由 m 个操作构成,
他知道这样的数据肯定打垮 spaly,但是国王还有很多很多其他的事情要做,所以统计每个操作所需要的实际代价
的任务就交给你啦。
数据中的操作分为五种:
1. 插入操作:向当前非空 spaly 中插入一个关键码为 key 的新孤立节点。插入方法为,先让 key 和根比较,如果
key 比根小,则往左子树走,否则往右子树走,如此反复,直到某个时刻,key 比当前子树根 x 小,而 x 的左子
树为空,那就让 key 成为 x 的左孩子; 或者 key 比当前子树根 x 大,而 x 的右子树为空,那就让 key 成为
x 的右孩子。该操作的代价为:插入后,key 的深度。特别地,若树为空,则直接让新节点成为一个单个节点的树
。(各节点关键码互不相等。对于“深度”的解释见末尾对 spaly 的描述)。
2. 单旋最小值:将 spaly 中关键码最小的元素 xmin 单旋到根。操作代价为:单旋前 xmin 的深度。
(对于单旋操作的解释见末尾对 spaly 的描述)。
3. 单旋最大值:将 spaly 中关键码最大的元素 xmax 单旋到根。操作代价为:单旋前 xmax 的深度。
4. 单旋删除最小值:先执行 2 号操作,然后把根删除。由于 2 号操作之后,根没有左子树,所以直接切断根和右子
树的联系即可(具体见样例解释)。 操作代价同 2 号操 作。
5. 单旋删除最大值:先执行 3 号操作,然后把根删除。 操作代价同 3 号操作。
对于不是 H 国的人,你可能需要了解一些 spaly 的知识,才能完成国王的任务:
a. spaly 是一棵二叉树,满足对于任意一个节点 x,它如果有左孩子 lx,那么 lx 的关键码小于 x 的关键码。
如果有右孩子 rx,那么 rx 的关键码大于 x 的关键码。
b. 一个节点在 spaly 的深度定义为:从根节点到该节点的路径上一共有多少个节点(包括自己)。
c. 单旋操作是对于一棵树上的节点 x 来说的。一开始,设 f 为 x 在树上的父亲。如果 x 为 f 的左孩子,那么
执行 zig(x) 操作(如上图中,左边的树经过 zig(x) 变为了右边的树),否则执行 zag(x) 操作(在上图中,将
右边的树经过 zag(f) 就变成了左边的树)。每当执 行一次 zig(x) 或者 zag(x),x 的深度减小 1,如此反复,
直到 x 为根。总之,单旋 x 就是通过反复执行 zig 和 zag 将 x 变为根。

挖掘

我们来挖掘这些操作的性质。
插入,一定会插入在其前驱和后继中深度较大点的下方。
证明:
首先splay按中序遍历有序。
一个叶子的父亲一定是这个点的前驱或后继,这个更显然了你脑补或者反证都能得到。
那么找到前驱pre和后继next。插入该点前pre和next也互为前驱和后继,新点要么在pre下面要么在next下面。容易讨论,pre在next上面时,新点是next的左儿子,否则新点是pre的右儿子。

旋最小(旋最大同理),那么最小那个点一定没有左儿子,而且容易讨论它旋转到根后,树的形态只有很微小的改变(该点的父亲的左儿子变为该点右儿子,该点变成根且右儿子变成原来的根)

删除只有一个儿子的根就更简单了。
找前驱和后继可以用set实现,其余部分无脑LCT。

#include<cstdio>
#include<algorithm>
#include<set>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100000+10;
struct dong{
    int x,y;
    friend bool operator <(dong a,dong b){
        return a.y<b.y||a.y==b.y&&a.x<b.x;
    }
} zlt;
multiset<dong> s;
multiset<dong>::iterator it;
int tree[maxn][2],size[maxn],father[maxn],pp[maxn],sta[maxn],key[maxn];
bool bz[maxn];
int left[maxn],right[maxn];
int i,j,k,l,t,n,m,x,tot,top,ans,root;
bool czy,gjx;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
int pd(int x){
    return tree[father[x]][1]==x;
}
void update(int x){
    size[x]=size[tree[x][0]]+size[tree[x][1]]+1;
}
void rotate(int x){
    int y=father[x],z=pd(x);
    father[x]=father[y];
    if (father[y]) tree[father[y]][pd(y)]=x;
    tree[y][z]=tree[x][1-z];
    if (tree[x][1-z]) father[tree[x][1-z]]=y;
    tree[x][1-z]=y;
    father[y]=x;
    if (pp[y]) pp[x]=pp[y],pp[y]=0;
    update(y);
    update(x);
}
void mark(int x){
    if (!x) return;
    bz[x]^=1;
    swap(tree[x][0],tree[x][1]);
}
void down(int x){
    if (bz[x]){
        mark(tree[x][0]);
        mark(tree[x][1]);
        bz[x]=0;
    }
}
void remove(int x,int y){
    top=0;
    while (x!=y){
        sta[++top]=x;
        x=father[x];
    }
    while (top) down(sta[top--]);
}
void splay(int x,int y){
    remove(x,y);
    while (father[x]!=y){
        if (father[father[x]]!=y)
            if (pd(x)==pd(father[x])) rotate(father[x]);else rotate(x);
        rotate(x);
    }
}
void access(int x){
    int y,z;
    splay(x,0);
    z=tree[x][1];
    if (z){
        pp[z]=x;
        father[z]=0;
    }
    tree[x][1]=0;
    update(x);
    while (1){
        y=pp[x];
        if (!y) break;
        splay(y,0);
        z=tree[y][1];
        if (z){
            pp[z]=y;
            father[z]=0;
        }
        tree[y][1]=x;
        father[x]=y;
        pp[x]=0;
        update(y);
        splay(x,0);
    }
}
void makeroot(int x){
    access(x);
    splay(x,0);
    mark(x);
}
void link(int x,int y){
    makeroot(y);
    splay(y,0);
    pp[y]=x;
}
void cut(int x,int y){
    makeroot(x);
    access(x);
    splay(y,0);
    pp[y]=0;
}
int getdep(int x){
    makeroot(root);
    access(x);
    splay(x,0);
    return size[tree[x][0]]+1;
}
int getfa(int x){
    makeroot(root);
    access(x);
    splay(x,0);
    int t=tree[x][0];
    if (!t) return 0;
    while (tree[t][1]){
        down(t);
        t=tree[t][1];
    }
    splay(t,0);
    return t;
}
void splaymin(){
    k=right[j];
    l=getfa(j);
    if (!l) return;
    cut(j,l);
    if (k) cut(j,k);
    if (k) link(l,k);
    left[l]=k;
    link(j,root);
    right[j]=root;
    root=j;
}
void splaymax(){
    k=left[j];
    l=getfa(j);
    if (!l) return;
    cut(j,l);
    if (k) cut(j,k);
    if (k) link(l,k);
    right[l]=k;
    link(j,root);
    left[j]=root;
    root=j;
}
void write(int x){
    if (!x){
        putchar('0');
        putchar('\n');
        return;
    }
    top=0;
    while (x){
        sta[++top]=x%10;
        x/=10;
    }
    while (top) putchar('0'+sta[top--]);
    putchar('\n');
}
int main(){
    freopen("splay.in","r",stdin);freopen("splay.out","w",stdout);
    m=read();
    while (m--){
        t=read();
        if (t==1){
            x=read();
            zlt.x=++tot;
            zlt.y=x;
            size[tot]=1;
            key[tot]=x;
            s.insert(zlt);
            it=s.find(zlt);
            if (it==s.begin()) czy=1;else czy=0;
            if ((++it)==s.end()) gjx=1;else gjx=0;
            it--;
            if (czy&&gjx) root=tot;
            else if (czy){
                k=(*(++it)).x;
                left[k]=tot;
                link(tot,k);
            }
            else if (gjx){
                k=(*(--it)).x;
                right[k]=tot;
                link(tot,k);
            }
            else{
                j=(*(--it)).x;it++;k=(*(++it)).x;
                if (getdep(j)<getdep(k)){
                    left[k]=tot;
                    link(tot,k);
                }
                else{
                    right[j]=tot;
                    link(tot,j);
                }
            }
            ans=getdep(tot);
        }
        else if (t==2||t==4){
            it=s.begin();
            j=(*it).x;
            ans=getdep(j);
            splaymin();
            if (t==4){
                root=right[j];
                if (root) cut(j,right[j]);
                zlt.x=j;zlt.y=key[j];
                s.erase(s.find(zlt));
            }
        }
        else{
            it=s.end();
            it--;
            j=(*it).x;
            ans=getdep(j);
            splaymax();
            if (t==5){
                root=left[j];
                if (root) cut(j,left[j]);
                zlt.x=j;zlt.y=key[j];
                s.erase(s.find(zlt));
            }
        }
        write(ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值