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

来源:互联网 发布:通信线路迁改优化 编辑:程序博客网 时间:2024/05/01 04:20

题目描述

传送门

题解

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

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

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

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

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

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

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

代码

#include<iostream>#include<cstring>#include<cstdio>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;            }        }    }}

总结

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

0 0
原创粉丝点击