1507: [NOI2003]Editor
Time Limit: 5 Sec Memory Limit: 162 MBSubmit: 3164 Solved: 1276
[ Submit][ Status][ Discuss]
Description
Input
输入文件editor.in的第一行是指令条数t,以下是需要执行的t个操作。其中: 为了使输入文件便于阅读,Insert操作的字符串中可能会插入一些回车符,请忽略掉它们(如果难以理解这句话,可以参考样例)。 除了回车符之外,输入文件的所有字符的ASCII码都在闭区间[32, 126]内。且行尾没有空格。 这里我们有如下假定: MOVE操作不超过50000个,INSERT和DELETE操作的总个数不超过4000,PREV和NEXT操作的总个数不超过200000。 所有INSERT插入的字符数之和不超过2M(1M=1024*1024),正确的输出文件长度不超过3M字节。 DELETE操作和GET操作执行时光标后必然有足够的字符。MOVE、PREV、NEXT操作必然不会试图把光标移动到非法位置。 输入文件没有错误。 对C++选手的提示:经测试,最大的测试数据使用fstream进行输入有可能会比使用stdio慢约1秒。
Output
输出文件editor.out的每行依次对应输入文件中每条GET指令的输出。
Sample Input
Insert 26
abcdefghijklmnop
qrstuv wxy
Move 15
Delete 11
Move 5
Insert 1
^
Next
Insert 1
_
Next
Next
Insert 4
.\/.
Get 4
Prev
Insert 1
^
Move 0
Get 22
Sample Output
abcde^_^f.\/.ghijklmno
HINT
Source
题解:块状链表模板题。
块状链表小结:参考自 论文 苏煜《对块状链表的一点研究》
1. 数组和链表对比:
操作 | 数组 | 链表 |
---|---|---|
存储结构 | 地址连续的存储单元,物理位置相邻 | 地址不连续,物理位置不相邻 |
定位 | O(1) | O(N) |
添加 | O(N) | O(1) |
删除 | O(N) | O(1) |
数组有很好的定位功能,一般对应于固定的长度,不适合添加/删除等操作,当数组有序时,可二分查找某值,效率很好,O(logN)。
链表添加删除效率极高,很适合这类操作。但定位效率很低。
2.块状链表
块状链表是对数组和链表的折中,集两者之长,在定位,添加删除的效率上都有大幅提升。
整体上使用链表,链表节点是一个大小适当的数组。
如下图:
3.基本操作
①:定位
从链表头开始往后扫,每个节点记录本节点合法数据的长度,最终会定位为某一个块及块内偏移。
②:分裂
将指定的块在指定的位置分裂成2个块。
③:合并
本块合并掉之后的那一块,前提是2块的有效数据长度之和 <= N (array的长度)
在定位,插入,删除的时候对本块进行合并将会减少块元素过少,否则有可能退化成普通的链表。
④:插入
在指定位置分裂,然后在本块后面插入若干个块,示意图如下:
⑤:删除
在指定的位置分裂,删除本块之后的若干块,示意图如下:
4.效率分析:
设数组大小为x,数据总数为N,则理想状况下分块树为N/x,则定位的时间复杂度为O(N/x),插入删除的时间复杂度为O(x)
令N/x = x , x = sqrt(N),即每次操作的时间复杂度大致为O(sqrt(N))
和平衡树O(logN)等相比还是有较大差距,但是其附加空间很少,仅为O(sqrt(N)),平衡树的附加空间为O(N)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1<<25;
const int blocksize=20000;
const int blocknum=N/blocksize*3;
int n,m;
int cur;
char str[N+110],s[100];
queue<int> q;
struct node
{
char data[blocksize+111];
int len,next; //记录块中元素的总长度和相邻的下一个块的编号,注意编号不一定是连续的
}; node a[blocknum+1000];
int newnode() //新开一个块
{
int temp=q.front(); q.pop(); return temp;
}
void delnode(int t)//把需要整个删除的块的编号扔进队列中,相当于内存回收,新开块时只需要从中提取编号
{
q.push(t);
}
void find(int &pos,int &now) //从第一个块开始搜索,搜索位置POS所属的块的编号
{
for (now=0;a[now].next!=-1&&pos>a[now].len;now=a[now].next) pos-=a[now].len;
}
void fillnode(int pos,int n,char data[],int next)
{
a[pos].next=next; a[pos].len=n;
memcpy(a[pos].data,data,n); //将data 中前n个元素复制到块pos中
}
void split(int pos,int p) // 将块pos从p前面一分为二,使块的长度不超过P
{
if (a[pos].len==p) return;
int t=newnode(); //定义新块
fillnode(t,a[pos].len-p,a[pos].data+p,a[pos].next);
a[pos].next=t; a[pos].len=p;
}
void maintain(int pos) //将所有的碎块合并
{
int t;
for (;pos!=-1;pos=a[pos].next){
for(t=a[pos].next;t!=-1&&a[pos].len+a[t].len<blocksize;t=a[t].next)
{
memcpy(a[pos].data+a[pos].len,a[t].data,a[t].len);
a[pos].len+=a[t].len; a[pos].next=a[t].next; delnode(t);
}
}
}
void insert(int pos,int n)
{
int now,i,t;
find(pos,now); split(now,pos); //now记录的是当前位置所在块的编号
for (i=0;i+blocksize<=n;i+=blocksize){
t=newnode();
fillnode(t,blocksize,str+i,a[now].next);
a[now].next=t;
now=t;
}
if (i<n) //插入字符的最后一段如果不能填满一整个块就单独处理一下
{
t=newnode();
fillnode(t,n-i,str+i,a[now].next);
a[now].next=t;
}
maintain(now);
}
void del(int pos,int n)
{
int i,now,t;
find(pos,now); split(now,pos); //删除的时候先处于第一个位置所在的块,通过拆块的方式使当前块中要删除的元素单独分到一个块中
for (i=a[now].next;i!=-1&&n>a[i].len;i=a[i].next) n-=a[i].len;
split(i,n); i=a[i].next;
for(t=a[now].next;t!=i;t=a[now].next) a[now].next=a[t].next,delnode(t);
maintain(now);
}
void get(int pos,int n) //将需要输出的元素复制到str数组中
{
int i,now,t;
find(pos,now); i=min(n,a[now].len-pos);
memcpy(str,a[now].data+pos,i);
for (t=a[now].next;t!=-1&&i+a[t].len<=n;t=a[t].next)
{
memcpy(str+i,a[t].data,a[t].len);
i+=a[t].len;
}
if (i<n&&t!=-1) memcpy(str+i,a[t].data,n-i);
str[n]=0;
}
void init() //预处理
{
for (int i=1;i<=blocknum;i++) q.push(i);
a[0].len=0; a[0].next=-1;
}
void read(int m) //读入字符串,注意在存储时都是从0开始填
{
int i=-1;
while (i<m-1)
{
i++;
char c=getchar();
str[i]=c;
if (c<32||c>126) i--;
}
}
int main()
{
init();
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%s",s);
if (s[0]=='M') scanf("%d",&cur); //将光标移动到第K个字符之后
if (s[0]=='I') { //在光标处插入长度为M的字符串
scanf("%d",&m);
read(m);
insert(cur,m);
}
if (s[0]=='P') cur--; //光标前移
if (s[0]=='N') cur++; //光标后移
if (s[0]=='D'){ //在光标处删除连续的m个字符
scanf("%d",&m);
del(cur,m);
}
if (s[0]=='G'){ //输出光标后连续M个字符
scanf("%d",&m);
get(cur,m);
puts(str);
}
}
}