区间翻转问题 Splay

NKOJ 2504 区间翻转问题

问题描述

给你一个长度为N的序列{ai}和M个操作
1.查询第k个数的值
2.将第k个数增加d
3.查询一段区间的和
4.查询一段区间的最大值
5.将一段区间镜面翻转(例如序列{1,2,3,4,5,6},将从2到5的区间翻转后得到序列{1,5,4,3,2,6})
对于除操作2,5以外的操作,输出相应的答案

输入格式

第一行两个正整数N,M
第二行N个整数,为初始的序列
第三行到底M+2行,每行若干个整数
·如果第一个数为1,那么后面一个正整数k,表示查询第k个数的值
·如果第一个数是2,那么后面两个正整数k,d,表示将ak增加d
·如果第一个数为3,那么后面两个正整数l,r,表示查询从al到ar的区间和
·如果第一个数为4,那么后面两个正整数l,r,表示查询从al到ar的最大值
·如果第一个数为5,那么后面两个正整数l,r,表示翻转从al到ar的这个区间

输出格式

除操作2,5外每个操作输出占一行,一个整数,为本次提问的答案

样例输入

6 8
1 2 3 4 5 6
1 4
3 2 5
4 2 2
5 2 5
3 1 3
5 2 5
2 5 1
4 1 6

样例输出

4
14
2
10
6

数据规模

2<=N<=100000
1<=M<=100000
原序列1<=ai<=1000
每次1<=k<=N,1<=l<=r<=N,1<=d<=1000

来源

感谢nodgd命题并提供数据


Splay裸题,主要是留个纪念。

首先按照编号建立平衡树,具体方法是加两个虚拟节点1,N+2,再将原数列中的数按key值为编号+1插入平衡树。

对于区间操作的整体思想是:提取一个区间[l,r],就把key为(l+1)-1的节点旋转到根,再把key为(r+1)+1的节点旋转到根的右儿子。此时这个节点的左子树就正好是需要提取的区间。这个由二叉检索树的性质是正确的。

于是,要得到区间和、区间极值,只需要维护以某个节点为根的子树的和、子树中的权值最大值即可。这些容易在旋转的同时更新。

修改某个点的权值,我采用的是先将该点旋转到根,再更新根节点的Max,Sum,val值。也可以在查找该点的同时更新路径上所有点的Max,Sum值。

最后是区间翻转问题。提取区间后,只需要打上lazy标记,访问到的时候下放,交换左右两棵子树即可。虽然Splay是自下往上的过程,但是Splay前一定要先找到需要Splay到根的点。这个过程是自上往下的,此时下放即可,不会造成操作的冲突。

注意lazy与线段树里略有不同:线段树中打上lazy标记的点一定是已经处理完该点的操作了,而本题中是下放时才进行操作。所以打lazy标记时应当用lazy^=1而不是lazy=1。


代码:

#include<stdio.h>
#include<algorithm>
#define MAXN 100005
using namespace std;

int Tmax(int x,int y,int z)
{
    if(x>=y&&x>=z)return x;
    if(y>=z)return y;
    return z;
}

int N;

int rt,tot,fa[MAXN],ls[MAXN],rs[MAXN],pri[MAXN],val[MAXN],lazy[MAXN],Sum[MAXN],Max[MAXN],Size[MAXN];

void Update(int x,int y)
{
    Size[y]=Size[ls[y]]+Size[rs[y]]+1;
    Size[x]=Size[ls[x]]+Size[rs[x]]+1;
    Sum[y]=Sum[ls[y]]+Sum[rs[y]]+val[y];
    Sum[x]=Sum[ls[x]]+Sum[rs[x]]+val[x];
    Max[y]=Tmax(Max[ls[y]],Max[rs[y]],val[y]);
    Max[x]=Tmax(Max[ls[x]],Max[rs[x]],val[x]);
}

void Putdown(int p)
{
    lazy[ls[p]]^=1;lazy[rs[p]]^=1;lazy[p]=0;
    swap(ls[p],rs[p]);
}

void Zig(int x)
{
    int y=fa[x],z=fa[y];
    if(z)
    {
        if(ls[z]==y)ls[z]=x;
        else rs[z]=x;
    }
    fa[x]=z;fa[y]=x;fa[rs[x]]=y;
    ls[y]=rs[x];rs[x]=y;
    Update(x,y);
}

void Zag(int x)
{
    int y=fa[x],z=fa[y];
    if(z)
    {
        if(ls[z]==y)ls[z]=x;
        else rs[z]=x;
    }
    fa[x]=z;fa[y]=x;fa[ls[x]]=y;
    rs[y]=ls[x];ls[x]=y;
    Update(x,y);
}

void Splay(int x,int t)
{
    int y,z;
    while(fa[x]!=t)
    {
        y=fa[x];z=fa[y];
        if(z==t)
        {
            if(ls[y]==x)Zig(x);
            else Zag(x);
        }
        else
        {
            if(ls[z]==y)
            {
                if(ls[y]==x)Zig(y),Zig(x);
                else Zag(x),Zig(x);
            }
            else
            {
                if(rs[y]==x)Zag(y),Zag(x);
                else Zig(x),Zag(x);
            }
        }
    }
    if(!t)rt=x;
}

void Ins(int k,int v)
{
    tot++;
    int p=rt;
    while(p)
    {
        Size[p]++;
        if(Max[p]<v)Max[p]=v;Sum[p]+=v;
        if(k<pri[p])
        {
            if(!ls[p]){ls[p]=tot;break;}
            p=ls[p];
        }
        else
        {
            if(!rs[p]){rs[p]=tot;break;}
            p=rs[p];
        }
    }
    Size[tot]=1;fa[tot]=p;pri[tot]=k;val[tot]=Max[tot]=Sum[tot]=v;
    Splay(tot,0);
}

int GetKth(int k)
{
    int p=rt;
    while(p)
    {
        if(lazy[p])Putdown(p);
        if(Size[ls[p]]+1==k)return p;
        if(Size[ls[p]]>=k)p=ls[p];
        else k-=Size[ls[p]]+1,p=rs[p];
    }
}

void Add(int k,int d)
{
    int p=GetKth(k+1);
    Splay(p,0);
    val[p]+=d;
    Max[p]=Tmax(val[p],Max[ls[p]],Max[rs[p]]);
    Sum[p]+=d;
}

void Prepare(int x,int y)
{
    int p,q;
    p=GetKth(x);Splay(p,0);
    q=GetKth(y+2);Splay(q,rt);
}

int GetSum(int x,int y)
{
    Prepare(x,y);
    return Sum[ls[rs[rt]]];
}

int GetMax(int x,int y)
{
    Prepare(x,y);
    return Max[ls[rs[rt]]];
}

void Rev(int x,int y)
{
    Prepare(x,y);
    int p=ls[rs[rt]];
    lazy[p]^=1;
}

int main()
{
    int M,i,op,x,y,p;

    scanf("%d%d",&N,&M);

    Ins(1,0);Ins(N+2,0);    
    for(i=1;i<=N;i++)
    {
        scanf("%d",&x);
        Ins(i+1,x);
    }

    while(M--)
    {
        scanf("%d",&op);
        if(op==1)
        {
            scanf("%d",&x);
            p=GetKth(x+1);Splay(p,0);
            printf("%d\n",val[p]);
        }
        else if(op==2)
        {
            scanf("%d%d",&x,&y);
            Add(x,y);
        }
        else if(op==3)
        {
            scanf("%d%d",&x,&y);
            printf("%d\n",GetSum(x,y));
        }
        else if(op==4)
        {
            scanf("%d%d",&x,&y);
            printf("%d\n",GetMax(x,y));
        }
        else
        {
            scanf("%d%d",&x,&y);
            Rev(x,y);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值