您的位置:首页 > 其它

bzoj1507 [NOI2003]Editor(块状链表)

2018-01-17 18:38 411 查看

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

15

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

[Submit][Status][Discuss]

分析:

块状链表的代码实现

块状链表的思路很好理解,代码实现还是比较好理解的

初始化

首先我们需要初始化

因为链表的特点就是空间上的不连续性,但是c++的链表非常的 * *,因此在这里我们选择用数组模拟链表

所以我们还是在代码中开够所有的结点

设置一个队列q,相当于内存池储存的是没有使用过的链表结点标号

void init()
{
for (int i=1;i<=blocknum;i++) q.push(i);
a[0].len=0; a[0].nxt=-1;
}


输入

读入的时候需要注意(样例就可以很好的说明)

插入的字符串有可能包含space,但是不包括“\n”,我们就不能用scanf

因此我们编写专门的函数,用getchar读入

void read(int len)    //需要特殊的读入函数 因为字符串中可能有空格
{
int i=0;
while (i<len)
{
char c=getchar();
s[i]=c;
i++;
if (c<32||c>126) i--;  //防止读入不合法字符
}
}


申请节点

申请新节点

int newnode()
{
int t=q.front(); q.pop();
return t;
}


删除结点

因为我们需要节约空间,因此删除的结点可以重新扔到内存池中,以备往后使用

void delnode(int t)
{
q.push(t);
}


定位

我们用链表的检索方式,直接沿着链暴力检索,最后定位到第now个块的第pos位

void find(int &pos,int &now)
//从第一个块开始搜索,搜索位置pos所属的块的编号
{
for (now=0;a[now].nxt!=-1&&pos>a[now].len;now=a[now].nxt) pos-=a[now].len;
}


fillnode

pos—>结点编号,n—>结点size,data[]—>字符数组,nxt—>下一块

memcpy(数组1的起始指针,数组2的起始指针,size)

效果:数组2 起始指针开始的size位 复制到了数组1中

void fillnode(int pos,int n,char data[],int nxt)
{
a[pos].nxt=nxt; a[pos].len=n;
memcpy(a[pos].data,data,n);
//将data中前n个元素复制到块pos中
}


分裂

我们直接申请一个新节点,

把当前结点的后半部分 copy到新节点中

不要忘记维护nxt

void divide(int pos,int p)
{
if (a[pos].len==p) return;
int t=newnode();
fillnode(t,a[pos].len-p,a[pos].data+p,a[pos].nxt);
a[pos].nxt=t;
a[pos].len=p;
}


合并

为了保证数据结构的时间复杂度

我们需要即使合并小的block:

从pos开始一条链检索下去,相邻两块block只要不超过blocksize就进行合并

void maintain(int pos)   //当前块与后一个块合并
{
int t;
for (;pos!=-1;pos=a[pos].nxt)
for (t=a[pos].nxt;t!=-1&&a[pos].len+a[t].len<blocksize;t=a[t].nxt)
{
memcpy(a[pos].data+a[pos].len,a[t].data,a[t].len);
a[pos].len+=a[t].len;
a[pos].nxt=a[t].nxt;
delnode(t);
}
}


插入

我们定位插入位置:第now块的第pos位

把第now块从pos分成两块,前半部分的标号就是now

我们把新字符分割成数块block,直接线性插入

因为插入了结点,块的大小会发生改变,因此我们需要合并一下小的block

void insert(int pos,int n)
{
int now,i,t;   //now记录当前位置所在的块的编号
find(pos,now);
divide(now,pos);
for (i=0;i+blocksize<=n;i+=blocksize)
{
t=newnode();
fillnode(t,blocksize,s+i,a[now].nxt);
a[now].nxt=t;
now=t;
}
if (i<n)        //剩下的元素不足以填满一个块,需要特殊处理一下
{
t=newnode();
fillnode(t,n-i,s+i,a[now].nxt);
a[now].nxt=t;
}
maintain(now);
}


删除

我们还是首先定位删除的位置:第now块的第pos位

通过拆块的方式使当前块中要删除的元素单独分到一个块中

最后如果剩下的不够一个block

我们还是把待删除的数据分到一个块内删除

因为删除了结点,块的大小会发生改变,因此我们需要合并一下小的block

void del(int pos,int n)
{
int i,now,t;
find(pos,now);
divide(now,pos);
//删除的时候先处于第一个位置所在的块,通过拆块的方式使当前块中要删除的元素单独分到一个块中
for (i=a[now].nxt;i!=-1&&n>a[i].len;i=a[i].nxt)
n-=a[i].len;
divide(i,n);
i=a[i].nxt;
for (t=a[now].nxt;t!=i;t=a[now].nxt)
a[now].nxt=a[t].nxt,delnode(t);
maintain(now);
}


询问

询问的思路和插入删除差不多

先定位查询位置

把查询的区间分成单独的block

复制到一个字符数组中,直接输出字符串即可

void get(int pos,int n)
{
int i,now,t;
find(pos,now);
i=min(n,a[now].len-pos);
memcpy(s,a[now].data+pos,i);
for (t=a[now].nxt;t!=-1&&i+a[t].len<=n;t=a[t].nxt)
{
memcpy(s+i,a[t].data,a[t].len);
i+=a[t].len;
}
if (i<n&&t!=-1) memcpy(s+i,a[t].data,n-i);
s
=0;  //防止出错结尾清零
}


#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>

using namespace std;

const int N=1<<25;
const int blocksize=20000;
const int blocknum=N/blocksize*3;
int n,cur;
char s[N+10];
queue<int> q;

struct node{
char data[blocksize];
int len,nxt; //记录块中的元素个数 下一个块的编号(模拟链表)
};
node a[blocknum+100];

int newnode() { int t=q.front(); q.pop(); return t; }

void delnode(int t) { q.push(t); }

void find(int &pos,int &now) //从第一个块开始搜索,搜索位置pos所属的块的编号
{
for (now=0;a[now].nxt!=-1&&pos>a[now].len;now=a[now].nxt) pos-=a[now].len;
}

void fillnode(int pos,int n,char data[],int nxt)
{
a[pos].nxt=nxt; a[pos].len=n;
memcpy(a[pos].data,data,n); //将data中前n个元素复制到块pos中
}

void divide(int pos,int p) { if (a[pos].len==p) return; int t=newnode(); fillnode(t,a[pos].len-p,a[pos].data+p,a[pos].nxt); a[pos].nxt=t; a[pos].len=p; }

void maintain(int pos) //当前块与后一个块合并 { int t; for (;pos!=-1;pos=a[pos].nxt) for (t=a[pos].nxt;t!=-1&&a[pos].len+a[t].len<blocksize;t=a[t].nxt) { memcpy(a[pos].data+a[pos].len,a[t].data,a[t].len); a[pos].len+=a[t].len; a[pos].nxt=a[t].nxt; delnode(t); } }

void insert(int pos,int n) { int now,i,t; //now记录当前位置所在的块的编号 find(pos,now); divide(now,pos); for (i=0;i+blocksize<=n;i+=blocksize) { t=newnode(); fillnode(t,blocksize,s+i,a[now].nxt); a[now].nxt=t; now=t; } if (i<n) //剩下的元素不足以填满一个块,需要特殊处理一下 { t=newnode(); fillnode(t,n-i,s+i,a[now].nxt); a[now].nxt=t; } maintain(now); }

void del(int pos,int n) { int i,now,t; find(pos,now); divide(now,pos); //删除的时候先处于第一个位置所在的块,通过拆块的方式使当前块中要删除的元素单独分到一个块中 for (i=a[now].nxt;i!=-1&&n>a[i].len;i=a[i].nxt) n-=a[i].len; divide(i,n); i=a[i].nxt; for (t=a[now].nxt;t!=i;t=a[now].nxt) a[now].nxt=a[t].nxt,delnode(t); maintain(now); }

void get(int pos,int n)
{
int i,now,t;
find(pos,now);
i=min(n,a[now].len-pos);
memcpy(s,a[now].data+pos,i);
for (t=a[now].nxt;t!=-1&&i+a[t].len<=n;t=a[t].nxt)
{
memcpy(s+i,a[t].data,a[t].len);
i+=a[t].len;
}
if (i<n&&t!=-1) memcpy(s+i,a[t].data,n-i);
s
=0;
}

void init() { for (int i=1;i<=blocknum;i++) q.push(i); a[0].len=0; a[0].nxt=-1; }

void read(int len) //需要特殊的读入函数 因为字符串中可能有空格
{
int i=0;
while (i<len)
{
char c=getchar();
s[i]=c;
i++;
if (c<32||c>126) i--; //
}
}

int main()
{
init();
scanf("%d",&n);
char opt[20];
int pos;
//所谓的光标实际上就是一个数组的下标
for (int i=1;i<=n;i++)
{
scanf("%s",opt);
if (opt[0]=='I')
{
scanf("%d",&pos);
read(pos);
insert(cur,pos);
}
else if (opt[0]=='M') scanf("%d",&cur); //直接移动光标
else if (opt[0]=='D')
{
scanf("%d",&pos);
del(cur,pos);
}
else if (opt[0]=='G')
{
scanf("%d",&pos);
get(cur,pos);
puts(s);
}
else if (opt[0]=='P') cur--; //光标前移
else if (opt[0]=='N') cur++; //光标后移
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: