单旋

在我的后园,可以看见墙外有两株树,一株是伸展树,还有一株也是伸展树

——-鲁迅



题意:

​ 如果 Splay 丧失了双旋的特性,只能单旋,那他就会成为一棵…

​ 然而世界上竟然有人专门用这种数据结构来坑你。

​ 给你一个 Spaly, 让你求每次给定操作的时间花费。

​ 给定的操作包括:

  • 插入一个点 (按两两不同的优先级排序)
  • 旋转最大值至根
  • 旋转最小值至根
  • 旋转最大值至根并删除
  • 旋转最小值至根并删除

分析:

​ 显然,直接模拟这种数据结构会耗尽你的人生。所以我们必须用更加优秀的数据结构来维护这虚伪的 Spaly 的形态。比如 Splay。

​ 假设我们有一棵 Spaly,进行单旋时,并不会破坏其二叉树的排序特性,所以我们知道其 DFS 序是不会改变的。所以我们其实可以用一棵 Splay 维护其 dfs 序,同时维护 Spaly 的形态。这样我们就可以以很高的效率保存并修改一个 Spaly。下面,我们需要知道 Spaly 的旋转如何对应 Splay 上的修改。

​ 观察发现,将 Spaly 的最小节点 x 旋转只根,相对关系改变的节点有 $X、ROOT、son[X]、fa[X]$。所以我们将 Splay 中维护的相关域修改即可。当然,节点相对位置的不变也是导致 Spaly 如此低效的原因,从 “Spaly 为啥慢” 这个方向入手也可以获得 “节点相对位置不变” 这个启发。

​ 从这简短的分析可以见得 HNOI2017DAY1T1 的思维难度并不是很高,而对我来说这题真正的挑战在于代码复杂度和细节。

细节:

​ 首先由于要维护的域比较多,各个域的更新顺序和对于 NULL 的特判比较复杂,所以难以调试和查错。

​ 比如对于 NULL 的特判:

void rside(int sd)
{
  ...
  if(!fa)return;
  if(sa)tre[sa].rf=fa;
  ...
}

​ 还有旋旋更健康:

void rside(int sd)
{
  ...
  splay(suf(root,tre[sa].x-1),0);
  ...
}

代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#define MX 102313

using namespace std;

struct node
{
    int x;
    int rf,rs[2],d,l;
    int f,s[2];
}tre[MX];
int n,cnt,root,rroot,COST;

int pos(int a){return (tre[tre[a].f].s[1]==a);}

void psd(int a)
{
    if(!tre[a].l)return;
    int ls=tre[a].s[0],rs=tre[a].s[1],l=tre[a].l;
    if(ls)tre[ls].d+=l,tre[ls].l+=l;
    if(rs)tre[rs].d+=l,tre[rs].l+=l;
    tre[a].l=0;
}

void rot(int a)
{
    int f=tre[a].f,g=tre[f].f,p=pos(a),q=pos(f);
    tre[f].s[p]=tre[a].s[!p],tre[a].s[!p]=f,tre[f].f=a;
    if(tre[f].s[p])tre[tre[f].s[p]].f=f;
    if(tre[a].f=g)tre[g].s[q]=a;
}

void splay(int a,int tar)
{
    while(tre[a].f!=tar)
        if(tre[tre[a].f].f==tar)rot(a);
        else if(pos(a)==pos(tre[a].f))rot(tre[a].f),rot(a);
        else rot(a),rot(a);
    if(!tar)root=a;
}

int pre(int a,int x)
{
    psd(a);
    if(!a)return 0;
    else if(x<=tre[a].x)return pre(tre[a].s[0],x);
    else
    {
        int ret=pre(tre[a].s[1],x);
        if(ret&&tre[ret].x>tre[a].x)return ret;
        else return a;
    }
}

int suf(int a,int x)
{
    psd(a);
    if(!a)return 0;
    else if(x>=tre[a].x)return suf(tre[a].s[1],x);
    else
    {
        int ret=suf(tre[a].s[0],x);
        if(ret&&tre[ret].x<tre[a].x)return ret;
        else return a;
    }
}

void insrt(int &a,int f,int x)
{
    psd(a);
    if(!a)
    {
        int p=pre(root,x),q=suf(root,x);
        a=++cnt;
        if(tre[p].d>tre[q].d)tre[a].rf=p,tre[a].d=tre[p].d+1,tre[p].rs[1]=a;
        else tre[a].rf=q,tre[a].d=tre[q].d+1,tre[q].rs[0]=a;
        COST=tre[a].d;
        tre[a].f=f,tre[a].x=x;
        if(tre[a].d==1)rroot=a;
        splay(a,0);
    }
    else if(x<tre[a].x)insrt(tre[a].s[0],a,x);
    else insrt(tre[a].s[1],a,x);
}

void rside(int sd)
{
    int a=root;
    while(tre[a].s[sd])psd(a),a=tre[a].s[sd];psd(a);
    COST=tre[a].d;
    int b=(sd?suf(root,tre[tre[a].rf].x):pre(root,tre[tre[a].rf].x));
    int sa=tre[a].rs[!sd],fa=tre[a].rf;
    if(!fa)return;
    tre[fa].rs[sd]=sa;
    if(sa)tre[sa].rf=fa;
    tre[a].rs[!sd]=rroot;
    tre[rroot].rf=a;
    tre[a].rf=0;
    rroot=a;
    splay(b,0);
    int c=tre[b].s[!sd];
    if(c)tre[c].d++,tre[c].l++;
    tre[a].d=1;
    splay(suf(root,tre[sa].x-1),0);
}

void del(int sd)
{
    rside(sd);
    int a=rroot;
    splay(a,0);
    int c=tre[a].s[!sd],d=tre[a].rs[!sd];
    if(c)tre[c].d--,tre[c].l--;
    tre[c].f=0;
    tre[d].rf=0;
    rroot=d;
    root=c;
}

inline int read()
{
    int x=0;char ch=getchar();
    while(!isdigit(ch))ch=getchar();
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return x;
}

int main()
{
    int o,a;
    n=read();
    for(int i=1;i<=n;i++)
    {
        o=read();
        if(o==1)a=read(),insrt(root,0,a),printf("%d\n",COST);
        else if(o==2)rside(0),printf("%d\n",COST);
        else if(o==3)rside(1),printf("%d\n",COST);
        else if(o==4)del(0),printf("%d\n",COST);
        else del(1),printf("%d\n",COST);
    }
    return 0;
}

分类: 文章

0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注