bzoj 1507: [NOI2003]Editor (块状链表)

1507: [NOI2003]Editor

Time Limit: 5 Sec   Memory Limit: 162 MB
Submit: 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

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

HINT

Source

[ Submit][ Status][ Discuss]

题解:块状链表模板题。

块状链表小结:参考自 论文 苏煜《对块状链表的一点研究》

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);   
        }    
    }    
}    



  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值