Spaly选讲……哦不,Splay

在(fsf的帮助下)水过(被虐)NOI2004和NOI2005,对Splay有了一些体会。
首先谈谈比较友善的NOI2004Plus:AHOI 文本编辑器。
题意大概是这样:
一个初始位置为0的光标,对该光标进行一些操作,前移,后移,或者跳转到某一位,支持在光标后进行插入,删除,翻转一段字符,支持光标后的询问并输出一定量的字符。
题目链接:文本编辑器
也可看图:
Spaly选讲……哦不,Splay_第1张图片
考虑对光标进行操作,那么光标前移后移跳转都能靠一个全局变量完成,而插入,删除,翻转,我们需要用Splay的性质来完成。前面说过Splay的单旋双旋,它不仅是降低操作时的时间复杂度,更重要的是,它能够提取区间

在讲提取区间之前,我们先复习一下Splay的单旋双旋操作。我们知道,任何情况下,一个节点总是能通过旋转从而到达树的根的,并且,如果Splay维护的是某个序列【有序的有序的有序的(重要的话说三遍)】,像这道题,我们可以知道,如果有序列f,且有i<j,那么我们对Splay的中序遍历必然是先遍历到i,再到达j。也就是说,在把i成功转到根之后,我们一定能在不动i的情况下,把j通过旋转变成i的右儿子[是直系亲属啊喂]

不知道为什么这个CSDN总是吞我的字……

那么我们会发现一个有趣的性质:
此时j的左子树是区间[i+1,j-1]。
所以,我们可以根据这一点,如果我们想要提取树上的区间[a,b],那么我们只需要把a-1放到根,b+1放到右儿子就可以了。这样的话,也就意味着我们可以通过两次Splay操作把区间提取出来,之后就想怎么做就怎么做了。
于是:
删除:提取区间[a,b],递归删它的左右儿子。删完之后记得把它的父亲更新一下并Splay到根。
翻转:提取区间[a,b],像搞线段树一样弄懒标记。[不会线段树的出门走右转看线段树(一)]。
好了,问题来了。插入怎么办?我们自然可以提取区间[a,a+1],然后把要插入的东西变成一个Splay,然后挂到左子树上,我自己也没觉得有什么不对……然而似乎标解不是这样……
①先把要插入的东西变成一个Splay树。
②递归下去找要插入的位置。
③找到位置之后,如果它没有右子树,直接挂到右子树,如果有,那么递归下去找该子树的左子树,直到找不到左儿子,挂到左儿子上。(也就是说,要严格保证新插入的插入位置和第一个元素它们的中序遍历相邻。)
贴带有满满的恶意风格的代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#define u t[x]
#define o t[y]
#define ulfc t[u.ch[0]]
#define urtc t[u.ch[1]]
#define tc ch[ty]
#define vc ch[!ty]
#define lc ch[0]
#define rc ch[1]
#define p u.par
using namespace std;
int rt=2,tot=2,top=0,n;
int stk[2000005];
int mark=0;
struct Tree{
    char w;
    int sz,ch[2],par;
    bool tag;
    Tree () { w = sz = lc = rc = par =tag= 0; }

    void rev () {tag^=1,swap(lc,rc);}

    void Set(char x){
        w=x;
        sz=1;
        tag=ch[0]=ch[1]=par=0;
    }
}t[1024*1024*3+5];
inline int Malloc(){return top?stk[top--]:++tot;}
inline void up(int x){u.sz=ulfc.sz+urtc.sz+1;}
char a[1024*1024*2+5];
inline void down(int x){if(!u.tag)return;ulfc.rev(),urtc.rev(),u.tag=0;}
inline void build(int &x,int l,int r){
    //cout<<"Build开始:\n";
    if(l>r){x=0;return;}
    int mid=(l+r)>>1;
    t[x=Malloc()].Set(a[mid]);
    build(u.lc,l,mid-1),build(u.rc,mid+1,r);
    ulfc.par=urtc.par=x;
    up(x);
    //cout<<"sz:"<<u.sz<<"x:"<<x<<endl;
    //cout<<"Build结束\n";
}
inline bool d(int x){return t[p].rc==x;}
inline int Find(int k){ //找出k在树中所在位置 
    ++k;
    int x=rt;
    for(down(x);k!=ulfc.sz+1;down(x)) 
        (k<=ulfc.sz)?x=u.lc:(k-=ulfc.sz+1,x=u.rc);
    return x;
}
inline void sc(int x,int y,bool ty){u.tc=y;o.par=x;}//y作为x的ch[ty]; 
inline void Fix(int x){if(p)Fix(p);down(x);}
inline void Rot(int x){
    //cout<<"以下是Rot操作"<<x<<endl; 
    int y=p;bool ty=d(x);
    sc(o.par,x,d(y)),sc(y,u.vc,ty),sc(x,y,!ty);
    //printf ("roting...\n");
    up(y),up(x);
    //cout<<"end" <<endl; 
}
inline void Splay(int x,int z=0){//bug: x==z;
    //cout<<"以下是Splay操作"<<x<<"到"<<z<<"\n"; 
    Fix(x);
    if(!z)rt=x;
    int y;
    while((y=p)!=z){
        if(o.par==z)
            Rot(x); 
        else 
            Rot(d(x)^d(y)?x:y),Rot(x);
    }
    //printf ("splaying...\n");

    up(x);
}
inline int Get(int l,int r){ //提取区间[l,r] 
    int x=Find(l-1),y=Find(r+1);
    Splay(x);
// system("pause");
    Splay(y,x);
    return o.lc; 
}
inline void Insert(int m){
    gets (a);
    for (int l = 0; l < m; gets (a + l + 1), l += strlen (a + l + 1)) ;
    int x,y=Find(mark);
    Splay(y);
    build(x,1,m);
    if(!o.rc)
        sc(y,x,1);      
    else {
        for(down(y=o.rc);o.lc;down(y=o.lc));
        sc(y,x,0);
    }
    Splay(x);
}
inline void Free(int x){
    if(!x)return;
    Free(u.lc),Free(u.rc);
    t[stk[++top]=x]=Tree();
}
inline void Del(int m){
    //system("pause");
    int x=Get(mark+1,mark+m);
    t[p].ch[0]=0; int _ = p;
    Free(x);
    up (_);
    Splay(_);
// system("pause");
}
inline void print(int x){
    if(!x)return;
    print(u.lc);
    putchar(u.w);
    print(u.rc);
}
inline void Get_let(){
    int x=Get(mark+1,mark+1);
    print(x);
    puts("");
}
int main (){
    //freopen("a.out","w",stdout);
    //t[0].ch[0]=t[0].ch[1]=t[0].sz=t[0].w=t[0].par=0;
    rt=2;
    t[1].sz=1;
    t[1].par=2;
    t[2].ch[1]=1;
    t[2].sz=2;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int m;char op[15];
        scanf("%s",op);
        //printf ("id: %d %c %d %d\n", i, op[0], t[rt].sz, t[0].sz);
        //dfs (rt);
        switch (op[0]){
            case 'M':
                scanf("%d",&m); 
                mark=m;
                break;
            case 'I':
                scanf("%d",&m);
                Insert(m);
                break;
            case 'D':
                scanf("%d",&m);
                Del(m);
                break;
            case 'G':
                Get_let();
                break;
            case 'P':
                mark--;
                break;
            case 'N':
                mark++;
                break;
            case 'R':
                scanf("%d",&m);
                int x=Get(mark+1,mark+m);
                u.rev(); 
                break;
        }
    }
    return 0;
}

另外,这不仅仅是教学。最重要的是提示我自己:
1.在光标移动时,光标位置和在树中的位置需要调用Find函数才能找到。
2.垃圾回收需要先Free完lc和rc,不然都会丢失。
3.哨兵的问题很大,请记住在Find函数中加一,并且记住初始化。
4.在你写Splay时,由于是树的缘故,请尝试用dfs找出所有数据。
5.如果处理一个点重复多次时,我们需要把size的加法变成+num,并且虽然我们认为num初值是1,但是要记住:空节点的num是0!
6.永远要仔细考虑好哨兵节点和空节点。
7.永远不要改t[0]这货……代码必须加诸如此类:
if(lc);if(rc);if(!x)return;
没错如果你不这样做的话,NOI2005会让你见识什么叫社会……
8.善于使用assert和cerr来解决程序上的问题,但别忘了assert会大大影响时间,所以一定要在提交之前去掉,否则会T。

你可能感兴趣的:(Spaly选讲……哦不,Splay)