HDU 4453 Looploop (2012年杭州赛区现场赛A题)

1.题目描述:点击打开链接

2.解题思路:本题是伸展树的基本题型,不过由于是第一次使用这种数据结构,先补了一下BST和Treap的基础知识,然后才开始学这种数据结构。不难发现,伸展树最基本且最核心的操作就是伸展操作,它能够将一个指定的结点通过旋转操作逐步转为树根。同时由于伸展树本质上也是一种BST,因此同时具备BST的基本功能,比如查询操作。我们每次都把箭头指向的位置作为树根。


首先是建树过程,我们从区间的中间位置开始,把它当做树根,如果m>L,那么递归建立左子树,如果m<R,那么递归建立右子树。这里有一个不太容易理解的地方是,为什么Splay操作不会影响我们查找原来数组中第k个位置的数是谁?因为如果把伸展树看做BST,那么这里的键值是子树的结点数。这样,只要一开始建树是从中间开始建立的,那么结点数的关系就不会改变了:数组下标为i的左子树永远都是i-1个结点,右子树永远都是n-i个结点。而Splay操作也是严格根据BST键值关系来旋转的,所以不会改变结点数目的关系。这样,不管伸展树变成了什么样子,我们都能快速找到原来数组下标为i的那个结点。由此还可以得到如下的推论:执行完Splay(root,1)后,这棵树只有右子树,左子树为空;执行完Splay(root,n)后,这棵树只有左子树,右子树为空。


下面来谈一谈如何利用伸展树来完成题目中给定的6种操作。

1.add X:要求把从箭头指向的位置到右边第k2个位置的所有数都增加X。显然,我们需要一个lazy标记来完成这一操作,不妨设为add。那么首先把k2位置伸展为树根,这样左子树的所有结点和根就是我们要进行add操作的所有结点。对根的value+add,并将左子树的lazy标记+add即可。


2.reverse:要求把从箭头指向的位置到右边第k1个位置的数反转。首先执行Splay(root,k1),并把它的右子树暂时分开,标记root->flip=1,翻转完毕后再将右子树合并回去。


3.insert X:要求在箭头指向的位置的下一个位置插入X。首先执行Splay(root,1),这样,只需要在root和root->ch[1]之间插入X即可,同时n++。


4.delete:要求删除箭头指向的位置,并把箭头向右移动一位。首先执行Splay(root,1),这样,所有结点都在右子树上,只需要把root设置为右子树的根即可,同时n--。


5.move X:当X==1时,将箭头向左移动一位;当X==2时,将箭头向右移动一位。当X==1时,先执行Splay(root,n),此时所有结点都在左子树上(注意此时a[n]是树根,a[1]已经在左子树上了),把根和左子树分离,同时对左子树执行Splay(tmp,1)(tmp即左子树树根)。然后让左子树成为根的右子树即可,即root->ch[1]=tmp。用同样的方法可以处理右子树。


6.query:输出箭头指向的数字。直接输出root->value即可。

由此,我们成功利用伸展树实现了上述的6种操作。

3.代码:

#include<iostream>
#include<algorithm>
#include<cassert>
#include<string>
#include<sstream>
#include<set>
#include<bitset>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<cctype>
#include<functional>
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

#define me(s)  memset(s,0,sizeof(s))
#define rep(i,n) for(int i=0;i<(n);i++)
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
typedef pair <int, int> P;


const int N=200000+5;
int a[N];
int n,m,k1,k2;
struct Node
{
    Node*ch[2];
    int size;
    int value;
    int flip,add;
    Node();
    Node(int);
    void pushup()
    {
        size=ch[0]->size+ch[1]->size+1;
    }
    void pushdown()
    {
        if(flip)
        {
            swap(ch[0],ch[1]);
            ch[0]->flip^=1;
            ch[1]->flip^=1;
            flip=0;
        }
        if(add)
        {
            value+=add;
            ch[0]->add+=add;
            ch[1]->add+=add;
            add=0;
        }
    }

    int rank()
    {
        return ch[0]->size+1;
    }
    int cmp(int k)
    {
        if(k==rank())return -1;
        return k<rank()?0:1;
    }
}null,*root,lk[N];

int cnt;

Node*newnode(int v)
{
    lk[cnt]=Node(v);
    return lk+cnt++;
}
Node::Node()
{
    ch[0]=ch[1]=&null;
    size=add=flip=0;
}
Node::Node(int v):value(v)
{
    ch[0]=ch[1]=&null;
    size=1;
    flip=add=0;
}
void rotate(Node*&o,int d)
{
    o->pushdown();
    Node*k=o->ch[d^1];
    o->ch[d^1]=k->ch[d];
    k->ch[d]=o;
    o->pushup();
    k->pushup();
    o=k;
}

void splay(Node*&o,int k)
{
    o->pushdown();
    int d=o->cmp(k);
    if(~d)
    {
        if(d)splay(o->ch[d],k-o->rank());
        else splay(o->ch[d],k);
        rotate(o,d^1);
    }
}

void  build(Node*&o,int l,int r)//建树
{
    int m=(l+r)>>1;
    o=newnode(a[m]);
    if(m>l)build(o->ch[0],l,m-1);
    if(m<r)build(o->ch[1],m+1,r);
    o->pushup();
}

void add(int x)//增加操作
{
    splay(root ,k2);
    root->value+=x;
    root->ch[0]->add+=x;
}

void reverse()//翻转操作
{
    splay(root ,k1);

    Node*tmp=root->ch[1];
    root->ch[1]=&null;

    root->flip=1;
    splay(root,k1);

    root->ch[1]=tmp;
    root->pushup();
}

void insert(int x)//插入操作
{
    splay(root,1);
    Node*tmp=newnode(x);
    tmp->ch[1]=root->ch[1];
    tmp->pushup();

    root->ch[1]=tmp;
    root->pushup();
    n++;
}
void remove()//删除操作
{
    splay(root,1);

    root=root->ch[1];
    n--;
}
void move(int x)//移动操作
{
    if(x==1)
    {
        splay(root,n);
        Node*tmp=root->ch[0];
        root->ch[0]=&null; //分离左子树,此时的根已经是a[n]了

        splay(tmp,1); //执行后,a[1]成为了左子树的树根
        root->ch[1]=tmp; //把左子树当做根的右子树
        root->pushup(); //更新size
    }
    else
    {
        splay(root,1);
        Node*tmp=root->ch[1];
        root->ch[1]=&null;

        splay(tmp,n-1); //注意;不要写成了splay(tmp,2),因为将1旋转为根后,左子树的树根的size=n,应该查找n-1的位置
        root->ch[0]=tmp;
        root->pushup();
    }
}


int main()
{
    int kase=1;
    while(~scanf("%d%d%d%d",&n,&m,&k1,&k2)&&(n||m||k1||k2))
    {
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        cnt=0;
        build(root,1,n);
        printf("Case #%d:\n",kase++);
        while(m--)
        {
            char cmd[10];
            scanf("%s",cmd);
            if(cmd[0]=='a')
            {
                int x;scanf("%d",&x);
                add(x);
            }
            if(cmd[0]=='r')reverse();
            if(cmd[0]=='i')
            {
                int x;scanf("%d",&x);
                insert(x);
            }
            if(cmd[0]=='d')remove();
            if(cmd[0]=='m')
            {
                int x;scanf("%d",&x);
                move(x);
            }
            if(cmd[0]=='q')
            {
                splay(root,1);
                printf("%d\n",root->value);
            }
        }
    }
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值