BZOJ1269 文本编辑器editor(伸展树)

题意

https://www.lydsy.com/JudgeOnline/problem.php?id=1269

思路

伸展树(\(\text{splay}\))功能比较齐全的模板,能较好的体现 \(\text{splay}\) 的功能,简单介绍一下 \(\text{splay}\)

基本的概念和函数

\(\text{splay}\) 是平衡树的一种,能在均摊 \(\log n\) 的时间复杂度内完成很多序列操作(序列就是树的中序遍历),核心是以下两个函数。

rotate

首先是旋转函数,\(\text{rotate}(x)\) 表示旋转 \(x\) 节点到它父亲的位置,它的父亲变成它的孩子,在旋转函数的过程中,原树的中序遍历保持不变,代码如下:

void rotate(int x)
{
    int y=fa[x],z=fa[y];
    //push_down(y),push_down(x);    有时需要
    int k=chk(x);
    ch[z][chk(y)]=x,fa[x]=z;
    ch[y][k]=ch[x][!k],fa[ch[x][!k]]=y;
    ch[x][!k]=y,fa[y]=x;
    //push_up(y),push_up(x);    //有时需要
}

网上有大量旋转函数过程图,这里不再解释。

splay

然后是伸展函数,\(\text{splay}(x,w)\) 表示将 \(x\) 通过 \(\text{rotate}\) 函数上旋至成为 \(w\) 的儿子,特别的,当 \(w=0\) 时,表示将 \(x\) 上旋至根节点。

有一点特别注意,当节点 \(x\)\(x\) 的父亲 \(y\)\(y\) 的父亲 \(z\) 三点共线,需要先转 \(y\) 再转 \(x\) ,否则转两次 \(x\) 。可以通过模拟一条链的旋转发现这种方法的优越性。

代码如下,可以写的很短:

void splay(int x,int w)
{
    push_down(x),push_up(x);
    while(fa[x]!=w)
    {
        int y=fa[x],z=fa[y];
        if(z!=w)(x==ch[y][1])==(y==ch[z][1])?rotate(y):rotate(x);
        rotate(x);
    }
    if(!w)rt=x;
}

由于我通过这个函数保证节点 \(x\) 已经被 \(\text{up down}\) 了,所以特地加了\(\text{up down}\) 防止不用进入循环体的情况。

基本的操作

构造

可以扔一个完美的 \(\text{splay}\) 上去,据说是对后面的操作在常数上有利。

void build(int &x,int f, int *arr,int l,int r)
{
    if(l>r)return;
    int mid=(l+r)>>1;
    x=++tot;
    ch[x][0]=ch[x][1]=0;
    fa[x]=f;
    pw[x]=arr[mid];
    //sum[x]=1,tag[x]=0;    在构造时清空会比较方便,如果有点权、标记的话要清空
    build(ch[x][0],x,arr,l,mid-1);
    build(ch[x][1],x,arr,mid+1,r);
    push_up(x);
}

前驱后继

对于一个中序单调的 \(\text{splay}\) ,可以通过查找的方法获得前驱后继,这也就是找第一个大于/小于(等于)一个数 \(v\) 的方法,即在 \(\text{splay}\) 上二分。

int get_pre(ll v,bool inc) //inc表示是否包含v
{
    int x=rt,tmp=-1;ll res=-1e18;
    while(x)
    {
        if(inc&&val[x]<=v||!inc&&val[x]<v)if(chk_max(res,val[x]))tmp=x;
        if(!ch[x][v>val[x])splay(x,0);
        x=ch[x][v>val[x]];
    }
    return tmp;
}
int get_nxt(ll v,bool inc)
{
    int x=rt,tmp=-1;ll res=1e18;
    while(x)
    {
        if(inc&&val[x]>=v||!inc&&val[x]>v)if(chk_min(res,val[x]))tmp=x;
        if(!ch[x][v>=val[x])splay(x,0);
        x=ch[x][v>=val[x]];
    }
    return tmp;
}

当然对于中序不一定单调,维护一个普通序列的 \(\text{splay}\) ,可以从结构上分析,利用旋转操作中序不变的性质。

int get_pre(int x)
{
    splay(x,0);
    if(!ch[x][0])return -1;
    x=ch[x][0];
    while(ch[x][1])x=ch[x][1];
    splay(x,0);
    return x;
}
int get_nxt(int x)
{
    splay(x,0);
    if(!ch[x][1])return -1;
    x=ch[x][1];
    while(ch[x][0])x=ch[x][0];
    splay(x,0);
    return x;
}

第K大值

与动点线段树写法类似,直接二分即可。

int get_Kth(int K)
{
    //K++;  因为有些题目需要加上极小值和极大值,为了下面调用的方便起见加了这一句话
    int x=rt;
    while(K!=sum[ch[x][0]]+1)
    {
        push_down(x);
        if(K<=sum[ch[x][0]])x=ch[x][0];
        else K-=sum[ch[x][0]]+1,x=ch[x][1];
    }
    splay(x,0);
    return x;
}

在这里说一下 \(\text{splay}\) 调用的时机,复杂度的证明我目前看不懂,但我知道由于 \(\text{splay}\) 是均摊 \(\log n\) ,所以只要是经过若干次循环迭代到的节点,都要上旋以防止复杂度堆积。

求一个点是第几大

int get_rank(int x)
{
    splay(x,0);
    return sum[ch[x][0]]+1;
//  return sum[ch[x][0]];   同理,在有极小值时采用这种写法
}

区间操作

我们需要的就是“取出”一个区间,一般采取这样的方法:

l=get_Kth(l-1),r=get_Kth(r+1);
splay(l,0),splay(r,l);

这样之后,\(\text{ch}[r][0]\) 就是我们需要的区间了,区间打标记,求和,挪位什么的就很好实现了。

\(\text{splay}\) 也被叫做分裂树,它能实现区间的,挪位,这是它很显著的优势。

代码

#include<bits/stdc++.h>
#define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
#define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
template<typename T,typename _T>inline bool chk_min(T &x,const _T y){return y<x?x=y,1:0;}
template<typename T,typename _T>inline bool chk_max(T &x,const _T y){return x<y?x=y,1:0;}
typedef long long ll;
const int N=3e6+5;
int ch[N][2],fa[N];char pw[N];
int sum[N];bool tag[N];
int rt,tot;
int n,mouse;
char str[N];
void init(){rt=tot=0;}
void tag_up(int x)
{
    tag[x]^=1;
    std::swap(ch[x][0],ch[x][1]);
}
void push_up(int x){sum[x]=sum[ch[x][0]]+sum[ch[x][1]]+1;}
void push_down(int x)
{
    if(!tag[x])return;
    if(ch[x][0])tag_up(ch[x][0]);
    if(ch[x][1])tag_up(ch[x][1]);
    tag[x]=0;
}
void rotate(int x)
{
    int y=fa[x],z=fa[y];
    push_down(y),push_down(x);
    int k=(x==ch[y][1]);
    ch[z][y==ch[z][1]]=x,fa[x]=z;
    ch[y][k]=ch[x][!k],fa[ch[x][!k]]=y;
    ch[x][!k]=y,fa[y]=x;
    push_up(y),push_up(x);
}
void splay(int x,int w)
{
    push_down(x),push_up(x);
    while(fa[x]!=w)
    {
        int y=fa[x],z=fa[y];
        if(z!=w)(x==ch[y][1])==(y==ch[z][1])?rotate(y):rotate(x);
        rotate(x);
    }
    if(!w)rt=x;
}
void build(int &x,int f,char *arr,int l,int r)
{
    if(l>r)return;
    int mid=(l+r)>>1;
    x=++tot;
    ch[x][0]=ch[x][1]=0;
    fa[x]=f;
    pw[x]=arr[mid];
    sum[x]=1,tag[x]=0;
    build(ch[x][0],x,arr,l,mid-1);
    build(ch[x][1],x,arr,mid+1,r);
    push_up(x);
}
int get_Kth(int K)
{
    K++;
    int x=rt;
    while(K!=sum[ch[x][0]]+1)
    {
        push_down(x);
        if(K<=sum[ch[x][0]])x=ch[x][0];
        else K-=sum[ch[x][0]]+1,x=ch[x][1];
    }
    splay(x,0);
    return x;
}
int insert(int pos,char *str,int n)
{
    int l=get_Kth(pos),r=get_Kth(pos+1);
    splay(l,0),splay(r,l);
    build(ch[r][0],r,str,0,n-1);
    splay(r,0);
}
void flip(int l,int r)
{
    l=get_Kth(l-1),r=get_Kth(r+1);
    splay(l,0),splay(r,l);
    tag_up(ch[r][0]);
}
void erase(int l,int r)
{
    l=get_Kth(l-1),r=get_Kth(r+1);
    splay(l,0),splay(r,l);
    ch[r][0]=0;
    splay(r,0);
}

int main()
{
    init();
    str[0]='0',str[1]='$';
    build(rt,0,str,0,1);
    mouse=0;
    scanf("%d",&n);
    while(n--)
    {
        int x;
        scanf("%s",str);
        if(str[0]=='M')
        {
            scanf("%d",&x);
            mouse=x;
        }
        else if(str[0]=='I')
        {
            scanf("%d",&x);getchar();
            FOR(i,0,x-1)str[i]=getchar();
            str[x]='\0';
            insert(mouse,str,x);
        }
        else if(str[0]=='D')
        {
            scanf("%d",&x);
            erase(mouse+1,mouse+x);
        }
        else if(str[0]=='R')
        {
            scanf("%d",&x);
            flip(mouse+1,mouse+x);
        }
        else if(str[0]=='G')
            printf("%c\n",pw[get_Kth(mouse+1)]);
        else if(str[0]=='P')mouse--;
        else if(str[0]=='N')mouse++;
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Paulliant/p/10359926.html