Spaly选讲……哦不,Splay

来源:互联网 发布:java银行项目面试技巧 编辑:程序博客网 时间:2024/06/06 04:25

在(fsf的帮助下)水过(被虐)NOI2004和NOI2005,对Splay有了一些体会。
首先谈谈比较友善的NOI2004Plus:AHOI 文本编辑器。
题意大概是这样:
一个初始位置为0的光标,对该光标进行一些操作,前移,后移,或者跳转到某一位,支持在光标后进行插入,删除,翻转一段字符,支持光标后的询问并输出一定量的字符。
题目链接:文本编辑器
也可看图:
图
考虑对光标进行操作,那么光标前移后移跳转都能靠一个全局变量完成,而插入,删除,翻转,我们需要用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.parusing 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。

0 0
原创粉丝点击