洛谷 P4008 [NOI2003]文本编辑器

先推广一下

求赞

我们考虑这样的一个问题

给你一个序列,要求你支持插入,删除,查询单点值

如果用数组,查询O(1),插入删除最坏O(n)

如果用链表,插入删除O(1),查询最坏O(n)

如果用平衡树……

不要跟我说平衡树

那么我们是否可以考虑:将一个一个的数组以链表的形式串起来,这样是否会提高操作的效率,又是否会降低一些操作的效率呢?

5ccaea7489faa.png

可以手动模拟一下各种操作

块状链表就是这样一个略显暴力的算法

但其复杂度较为优秀,所以在很多地方的应用都非常广

用一句话说叫“弱弱联合”

码量大,但极易理解,打着打着就打出两百K

先介绍一下比较基本的操作吧

Spilt

当一个块的长度过大,我们就可以考虑将其分裂成两个较小的块。

在处理类似于插入或者删除这类操作时,我们可以先从当前位置将其分裂成两个块,这样就可以十分方便的进行操作了。

5ccaeb4da07e5.png

Merge

同理,就是\(Split\)的逆运算。

部分Maintain

姑且称其为部分maintain吧,我也不知道叫什么。

在进行操作时,我们可能会使得一些块过大,一些块过小。

所以我们需要通过\(Spilt\)或者\(Merge\)来调整。

我们发现,在进行操作时所需要考虑的需要维护的块:区间前的那一块与区间开头块;区间末尾块与区间后的那一块。

这样做可能会使得块状链表没有在经过完整maintain操作时平衡,但会大大减少维护时的常数,而平衡程度也可以接受。

一般采用的维护方法:保证相邻两块大小加起来大于\(\sqrt{n}\),但每块大小不超过\(\sqrt{n}\),这样可以较好的维护平衡,同时不用考虑当块较大时的\(Split\)操作。

这是作者经过权衡后得出的做法,实测复杂度优秀,复杂度为\(O(1)\)


然后,我们切入正题。

Insert

查找光标块内的位置,在此位置将块分裂,然后将字符串一块一块地插入

Delete

同理

Get

不需要分裂,直接利用\(memcpy\)函数,对其进行复制粘贴即可

代码中有较详细注释,贴代码

#include<bits/stdc++.h>
using namespace std;
char xch,xB[1<<15],*xS=xB,*xTT=xB;
#define getc() (xS==xTT&&(xTT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xTT)?0:*xS++)
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'|ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}//为了使程序跑得更快所使用的读入优化 
const int maxn=2e3+10;
struct node{
    int nex,siz;//每一块数组的后继以及大小 
    char a[maxn<<1];
}b[maxn<<2];
int pool[maxn<<2],cnt,curpos;//内存池、指针以及当前光标位置 
inline int modi(){return pool[cnt++];}//内存分配 
inline void dele(int x){pool[--cnt]=x;}//内存回收 
inline void init()
{
    for(int i=1;i<(maxn<<2);++i) pool[i]=i;//维护内存池,动态分配回收内存 
    cnt=1;
    b[0].siz=0,b[0].nex=-1;//新建一个0号节点,方便操作 
}
inline void add(int x,int y,int num,char c[])//在第x块后添加一个编号为y的块,长度为num 
{
    if(y!=-1)
    {
        b[y].nex=b[x].nex,b[y].siz=num;
        memcpy(b[y].a,c,num);
    }
    b[x].nex=y;
}
inline void merge(int x,int y)//将第x块和第y块合并 
{
    memcpy(b[x].a+b[x].siz,b[y].a,b[y].siz);
    b[x].siz+=b[y].siz,b[x].nex=b[y].nex;
    dele(y);
}
inline void split(int cur,int pos)//将第cur块从pos处分割 
{
    if(cur==-1||pos==b[cur].siz) return ;
    add(cur,modi(),b[cur].siz-pos,b[cur].a+pos);
    b[cur].siz=pos;
}
inline int pos(int &x)//寻找当前光标所在的块和块内位置 
{
    int now=0;
    while(now!=-1&&x>b[now].siz) x-=b[now].siz,now=b[now].nex;
    return now;
}
inline void insert(int p,int num,char c[])//在p位置之后插入长度为num的字符串 
{
    int now=pos(p);
    split(now,p);
    int tot=0,nb,st=now;
    while(tot+maxn<=num)//维护块状链表平衡 
    {
        nb=modi();
        add(now,nb,maxn,c+tot);
        tot+=maxn;
        now=nb;
    }
    if(num-tot)
        nb=modi(),add(now,nb,num-tot,c+tot);
    if(b[now].siz+b[nb].siz<maxn&&nb!=-1)//不用对整个链表进行判断,部分maintain 
        merge(now,nb),nb=b[now].nex;
    if(b[st].siz+b[b[st].nex].siz<maxn&&b[st].nex!=-1)//同理 
        merge(st,b[st].nex);
//    maintain();
}
inline void erase(int p,int num)//在p位置之后删除长度为num的字符串
{
    int now=pos(p);
    split(now,p);
    int nex=b[now].nex;
    while(nex!=-1&&num>b[nex].siz)
        num-=b[nex].siz,nex=b[nex].nex;
    split(nex,num);
    nex=b[nex].nex;
    for(int i=b[now].nex;i!=nex;i=b[now].nex)
        b[now].nex=b[i].nex,dele(i);
    while(b[now].siz+b[nex].siz<maxn&&nex!=-1)//不用对整个链表进行判断,部分maintain 
        merge(now,nex),nex=b[now].nex;
//    maintain();
}
char ans[20000000];
inline void get(int p,int num)//输出p位置后长度为num的字符串
{
    int cur=pos(p);
    int tot=b[cur].siz-p;
    if(num<tot) tot=num;
    memcpy(ans,b[cur].a+p,tot);
    int now=b[cur].nex;
    while(now!=-1&&num>=tot+b[now].siz)
    {
        memcpy(ans+tot,b[now].a,b[now].siz);
        tot+=b[now].siz,now=b[now].nex;
    }
    if(num-tot>0&&now!=-1)
        memcpy(ans+tot,b[now].a,num-tot);
    ans[num]='\0';//为了不清空,用\0结束 
    printf("%s\n",ans);
}
inline char opt()
{
    char c=getc();
    while(c!='M'&&c!='I'&&c!='D'&&c!='G'&&c!='P'&&c!='N') c=getc();
    return c;
}//为了不与读入优化冲突 
int main()
{
//  freopen("3.in","r",stdin);
//  freopen("3.ans","w",stdout);
    init();
    int m;

    scanf("%d",&m);
    for(int i=1;i<=m;++i)
    {
        switch(opt())
        {
            case 'M':curpos=read();break;
            case 'I':
                int tmp;
                tmp=read();
                for(int i=0;i<tmp;++i)
                {
                    ans[i]=getc();
                    if(ans[i]<32||ans[i]>126) --i;
                }
                insert(curpos,tmp,ans);
                    break;
            case 'D':
                tmp=read();
                erase(curpos,tmp);
                break;
            case 'G':
                tmp=read();
                get(curpos,tmp);
                break;
            case 'P':--curpos;break;
            case 'N':++curpos;break;
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/HenryHuang-Never-Settle/p/10803406.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值