[BZOJ1861][ZJOI2006]书架(平衡树splay)

题目描述

传送门

题解

splay模板题(笑
对于基础算法这个熟练程度怎么去省选啊= =

题目中所给的编号即为平衡树中的点权。
开一个数组记录一下权值为i的在树中的变化是多少,每次询问的时候直接找。
对于Top操作:
如果树里只有一个点,什么都不用干;
否则,
首先把这个点删除,
然后找到树中的第一个点,
将这个点转到根,
将删除的那个点插到根的右儿子;

对于Bottom操作:
如果树里只有一个点,什么都不用干;
否则,
首先把这个点删除,
然后找到树中的第n-1个点,
注意删除了之后树里只有n-1个点,不能寻找第n个点!
将这个点转到根,
将删除的那个点插到根的右儿子;

对于Insert操作:
0的话什么都不用干;
如果是1或-1的话,
首先要判断这个点是否已经在树的最后一个和第一个,是的话什么都不用干,
否则,
将其中的一个转到根,另一个转到根的一个儿子,
交换这两个点,
注意这里会有一大堆鬼畜的父子关系变化(具体看代码)
(hxy神犇只find了之后交换了两个点的权值,Orz这种优越的姿势)

对于Ask操作:
将查询的这个点转到根,
返回根的左儿子的大小即为答案

对于Query操作:
做一个find就好啦!

具体见代码,有很详细的注释。

代码

#include
#include
#include
using namespace std;

const int max_n=800005;
const int max_N=max_n*2;

int n,m,s,t;
char opt[10];
int a[max_n],k[max_n],loc[max_n];
int ch[max_N][2],f[max_N],size[max_N],key[max_N];
int root,sz;

inline void clear(int x){
    ch[x][0]=ch[x][1]=f[x]=size[x]=key[x]=0;
}
inline int get(int x){
    return ch[f[x]][1]==x;
}
inline void update(int x){
    if (x){
        size[x]=1;
        if (ch[x][0]) size[x]+=size[ch[x][0]];
        if (ch[x][1]) size[x]+=size[ch[x][1]];
    }
}
inline void rotate(int x){
    int old=f[x],oldf=f[old],which=get(x);
    ch[old][which]=ch[x][which^1];
    f[ch[old][which]]=old;
    ch[x][which^1]=old;
    f[old]=x;
    f[x]=oldf;
    if (oldf) ch[oldf][ch[oldf][1]==old]=x;
    update(old);
    update(x);
}
inline void splay(int x,int tar){
    for (int fa;(fa=f[x])!=tar;rotate(x))
      if (f[fa]!=tar)
        rotate((get(x)==get(fa))?fa:x);
    if (!tar) root=x;
}

inline int build(int l,int r,int fa){
    if (l>r) return 0;
    int mid=(l+r)>>1;

    int now=++sz;
    //每个点的权值为每个点的编号,loc记录每个编号在树中的位置 
    key[now]=k[mid]; loc[k[mid]]=now; f[now]=fa;
    int lch=build(l,mid-1,now);
    int rch=build(mid+1,r,now);
    ch[now][0]=lch; ch[now][1]=rch;
    update(now);
    return now;
}
inline int find(int x){
    int now=root;
    while (1){
        if (ch[now][0]&&x<=size[ch[now][0]])
          now=ch[now][0];
        else{
            int temp=1;
            if (ch[now][0]) temp+=size[ch[now][0]];
            if (x==temp) return now;
            x-=temp;
            now=ch[now][1];
        }
    }
}
inline int pre(){
    int now=ch[root][0];
    while (ch[now][1]) now=ch[now][1];
    return now;
}
inline int next(){
    int now=ch[root][1];
    while (ch[now][0]) now=ch[now][0];
    return now;
}
inline void del(int x){
    splay(x,0);

    if (!ch[root][0]&&!ch[root][1]){
        clear(root);
        root=0;
        return;
    }
    if (!ch[root][0]){
        int oldroot=root; root=ch[oldroot][1]; f[root]=0; clear(oldroot); return;
    }
    if (!ch[root][1]){
        int oldroot=root; root=ch[oldroot][0]; f[root]=0; clear(oldroot); return;
    }
    int leftbig=pre(),oldroot=root;
    splay(leftbig,0);
    ch[root][1]=ch[oldroot][1];
    f[ch[oldroot][1]]=root;
    clear(oldroot);
    update(root);
}
inline void Swap(int x,int y,int opt){
    //这里x表示的是要换到根的东西,y表示的是根 
    //交换两个点的size 
    swap(size[x],size[y]);
    //lchx和rchx表示的是换到根的东西的两个儿子 
    int lchx=ch[x][0]; int rchx=ch[x][1];
    //lchy和rchy表示的是根的两个儿子 
    int lchy=ch[y][0]; int rchy=ch[y][1];
    //先将两个东西的两个儿子交换一下 
    ch[x][0]=lchy; ch[x][1]=rchy;
    ch[y][0]=lchx; ch[y][1]=rchx;
    //现在的根x的opt指向的那个儿子改为原来的根y 
    ch[x][opt]=y;
    //原来的根y的父亲改为现在的根x,现在的根x的父亲为0 
    f[y]=x; f[x]=0;
    //原来的根的另一个儿子指向现在的根 
    if (opt==0) f[rchy]=x;
    else f[lchy]=x; 
    //原来的换成根的那个东西的两个儿子的父亲改成根 
    f[lchx]=f[rchx]=y;
    root=x;
}

int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i)
      scanf("%d",&k[i]);
    //k表示每一个点的编号,在树中的权值 
    root=build(1,n,0);
    for (int i=1;i<=m;++i){
        scanf("%s",opt);
        scanf("%d",&s);
        switch(opt[0]){
            case 'T':{
                if (size[root]==1) continue; 
                //num为权值为s的点在树中的位置 
                int num=loc[s];
                //删除这个点 
                del(num);
                //find找树中位置第几个的点的编号,aa为第一个点的编号 
                int aa=find(1);
                //将第一个点转到根 
                splay(aa,0);
                //将当前点插到根的左子树 
                ch[root][0]=++sz;
                f[sz]=root; size[sz]=1; key[sz]=s; loc[s]=sz;
                update(root);
                break;
            }
            case 'B':{
                if (size[root]==1) continue;
                int num=loc[s];
                del(num);
                //因为删去了一个,所以应该为n-1个 
                int aa=find(n-1);
                //将第n-1个点转到根 
                splay(aa,0);
                //将当前点插到根的右子树 
                ch[root][1]=++sz;
                f[sz]=root; size[sz]=1; key[sz]=s; loc[s]=sz;
                update(root);
                break;
            }
            case 'I':{
                if (size[root]==1) continue;
                scanf("%d",&t);
                if (!t) continue;
                if (t==-1){
                    int num=loc[s];
                    //将当前点转到根 
                    splay(num,0);
                    //如果没有左儿子,即它为序列的第一个,什么都不用干 
                    if (!ch[root][0]) continue;
                    //将当前点的前驱转到根的左儿子 
                    splay(pre(),num);
                    //交换根和根的左儿子 
                    Swap(ch[root][0],root,0);
                }
                else{
                    int num=loc[s];
                    //将当前点转到根 
                    splay(num,0);
                    //如果没有右儿子,即它为序列的最后一个,什么都不干 
                    if (!ch[root][1]) continue;
                    //将当前点的后继转到根的右儿子 
                    splay(next(),num);
                    //交换根和根的右儿子 
                    Swap(ch[root][1],root,1);
                }
                break;
            }
            case 'A':{
                int num=loc[s];
                //将当前点转到根 
                splay(num,0);
                //根的左儿子的大小即为前面有多少个 
                printf("%d\n",size[ch[root][0]]);
                break;
            }
            case 'Q':{
                //aa为第s本书的编号 
                int aa=find(s);
                //输出这个编号对应的权值 
                printf("%d\n",key[aa]);
                break;
            }
        }
    }
}

总结

这道题给我最重要的经验是父子关系的改变:
改变某两个点,有影响的点要向上向下都扩展一层,即离他们远一层的父亲和儿子;
这两个点的父子关系也要发生变化;
如果与根有牵扯,最后要确定根

你可能感兴趣的:(题解,平衡树,省选)