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。
- Spaly选讲……哦不,Splay
- Spaly学习
- 被splay纠结了……
- 【codevs 1230】元素查找 splay……
- HNOI2017 spaly题解
- [BZOJ2761][JLOI2011]不重复数字(splay)
- SPLAY
- splay
- splay
- splay
- Splay
- Splay
- splay
- splay
- splay
- splay
- Splay
- splay
- android加载网页隐藏某些内容
- invalid specification for system parameter LOCAL_LISTENER
- iOS使用Xcode7的Instruments检测解决iOS内存泄露
- MyEclipse 2015优化技巧
- 选择排序
- Spaly选讲……哦不,Splay
- 深入理解Javascript面向对象编程
- C++操作符重载 友元基础教程
- Java Lambda表达式入门
- Arraylist
- 去掉tableview中section的headerview粘性
- Hashcode
- Jar包冲突解决方法
- cocos2d-js 计时器例子