1269: [AHOI2006]文本编辑器editor
Time Limit: 10 Sec
Memory Limit: 162 MB
Submit: 2017
Solved: 733
[ Submit][ Status]
Description
这些日子,可可不和卡卡一起玩了,原来可可正废寝忘食的想做一个简单而高效的文本编辑器。你能帮助他吗?为了明确任务目标,可可对“文本编辑器”做了一个抽象的定义: 文本:由0个或多个字符构成的序列。这些字符的ASCII码在闭区间[32, 126]内,也就是说,这些字符均为可见字符或空格。光标:在一段文本中用于指示位置的标记,可以位于文本的第一个字符之前,文本的最后一个字符之后或文本的某两个相邻字符之间。文本编辑器:为一个可以对一段文本和该文本中的一个光标进行如下七条操作的程序。如果这段文本为空,我们就说这个文本编辑器是空的。 编写一个程序: 建立一个空的文本编辑器。 从输入文件中读入一些操作指令并执行。 对所有执行过的GET操作,将指定的内容写入输出文件。
Input
输入文件中第一行是指令条数N,以下是需要执行的N个操作。除了回车符之外,输入文件的所有字符的ASCII码都在闭区间[32, 126]内。且行尾没有空格。
Output
依次对应输入文件中每条GET指令的输出,不得有任何多余的字符。
Sample Input
10
Insert 13
Balanced eert
Move 2
Delete 5
Next
Insert 7
editor
Move 0
Get
Move 11
Rotate 4
Get
Sample Output
B
t
HINT
对输入数据我们有如下假定: MOVE操作不超过50 000个,INSERT、DELETE和ROTATE操作作的总个数不超过6 000,GET操作不超过20 000个,PREV和NEXT操作的总个数不超过20 000。 所有INSERT插入的字符数之和不超过2M(1M=1 024*1 024)。 DELETE操作、ROTATE操作和GET操作执行时光标后必然有足够的字符。MOVE、PREV、NEXT操作不会把光标移动到非法位置。 输入文件没有错误。
Source
splay操作的模板题。
这道题要求维护一个数列,支持一系列操作(光标的位置用一个pos记录即可):
Move(k):直接把pos赋值为k+1(为什么是k+1,后面再说)
Insert(n,str):先把pos旋转到根节点,再把pos+1旋到根结点的儿子,然后直接在pos+1的左儿子插入str即可
Delete(n):先把pos旋转到根节点,再把pos+n+1旋到根结点的儿子,然后删除pos+k+1的左儿子
Rotate(n):先把pos旋转到根节点,再把pos+n+1旋到根结点的儿子,然后把pos+k+1的左儿子的rev异或
Get():先把pos旋转到根节点,输出根节点右儿子中最小的那个结点所存储的字符
Prev():直接把pos--
Next():直接把pos++
为什么要在Move(k)把pos赋值为k+1,而不是k?
因为在后面的插入删除反转的操作中,要将当前位置的前一位转到根结点,如果当前位置是第一位就没有可转的了。所以在最开始的时候,先插入两个' ',表示开头和结尾,就不会出错了。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <cmath>
#define maxn 1024*1024*3
using namespace std;
int tot=0,pos;
struct splay
{
int rev,l,r,fa,size;
char data;
}a[maxn];
char str[maxn];
int root=0,q,k;
void Push_up(int x)
{
a[x].size=a[a[x].l].size+a[a[x].r].size+1;
}
void Push_down(int x)
{
if (a[x].rev)
{
swap(a[x].l,a[x].r);
a[x].rev=0;
a[a[x].l].rev^=1;
a[a[x].r].rev^=1;
}
}
void zig(int x)
{
int y=a[x].fa;
int z=a[y].fa;
Push_down(y);
Push_down(x);
a[x].fa=z,a[y].fa=x;
a[y].l=a[x].r,a[a[x].r].fa=y,a[x].r=y;
if (a[z].l==y) a[z].l=x;
else a[z].r=x;
Push_up(y);
}
void zag(int x)
{
int y=a[x].fa;
int z=a[y].fa;
Push_down(y);
Push_down(x);
a[x].fa=z,a[y].fa=x;
a[y].r=a[x].l,a[a[x].l].fa=y,a[x].l=y;
if (a[z].l==y) a[z].l=x;
else a[z].r=x;
Push_up(y);
}
void splay(int x,int s)
{
Push_down(x);
while (a[x].fa!=s)
{
int y=a[x].fa;
int z=a[y].fa;
if (a[y].fa==s)
{
if (x==a[y].l) zig(x);
else zag(x);
break;
}
if (y==a[z].l)
{
if (x==a[y].l) zig(y),zig(x);
else zag(x),zig(x);
}
else
{
if (x==a[y].r) zag(y),zag(x);
else zig(x),zag(x);
}
}
Push_up(x);
if (s==0) root=x;
}
int Getmin(int x)
{
Push_down(x);
while (a[x].l)
{
x=a[x].l;
Push_down(x);
}
return x;
}
void New_Node(int &x,int fa,char key)
{
x=++tot;
a[x].fa=fa;
a[x].rev=a[x].l=a[x].r=0;
a[x].data=key;
}
void Build(int &x,int fa,int l,int r,char *str)
{
if (l>r) return;
int m=(l+r)>>1;
New_Node(x,fa,str[m]);
Build(a[x].l,x,l,m-1,str);
Build(a[x].r,x,m+1,r,str);
Push_up(x);
}
int Findkth(int x,int k)
{
Push_down(x);
int s=a[a[x].l].size;
if (s+1==k) return x;
if (s>=k) return Findkth(a[x].l,k);
return Findkth(a[x].r,k-1-s);
}
void Insert(char *str)
{
int x=Findkth(root,pos);
splay(x,0);
x=Getmin(a[root].r);
splay(x,root);
Build(a[a[root].r].l,a[root].r,0,strlen(str)-1,str);
}
void Delet(int k)
{
int x=Findkth(root,pos);
splay(x,0);
int y=Findkth(root,pos+k+1);
splay(y,root);
a[a[y].l].fa=0;
a[y].l=0;
Push_up(a[root].r);
Push_up(root);
}
void Reserve(int k)
{
int x=Findkth(root,pos);
splay(x,0);
int y=Findkth(root,pos+k+1);
splay(y,root);
a[a[y].l].rev^=1;
}
void Print()
{
int x=Findkth(root,pos);
splay(x,0);
int y=Getmin(a[root].r);
printf("%c\n",a[y].data);
}
int main()
{
scanf("%d",&q);
pos=1;
for (int i=0;i<=1;i++)
str[i]=' ';
Build(root,0,0,1,str);
while (q--)
{
scanf("%s",str);
if (str[0]=='I')
{
scanf("%d",&k);
getchar();
gets(str);
Insert(str);
}
else if (str[0]=='M')
{
scanf("%d",&k);
pos=k+1;
}
else if (str[0]=='D')
{
scanf("%d",&k);
Delet(k);
}
else if (str[0]=='R')
{
scanf("%d",&k);
Reserve(k);
}
else if (str[0]=='G')
{
Print();
}
else if (str[0]=='P')
pos--;
else
pos++;
}
return 0;
}
小结:
1.一开始提交一直RE,结果是字符数组只开了100!!!以后要注意检查空间是否够用。
2.splay可以维护有序的数列,而数列分为两种:以权值为关键字和以位置为关键字,本题是以位置为关键字
3.splay(x,s)的时候,注意在旋转前先把标记下传,至于上传标记:zigzag中只要上传y的标记,而x的标记在旋转标记后再上传,这样不仅可以保证正确性,而且减小代码的常数。
附上Crash《运用伸展树解决数列维护问题》的解释: