区间翻转问题

问题描述

给你一个长度为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


题解

我们维护一棵平衡树,键值为各点的编号。一个点的左子树的实际位置在这个点之前,右子树的实际位置在这个点之后。初始时任一点的左子树(如果有的话)编号全部小于该点编号,右子树编号全部大于该点编号。并记录好每个点为根的树的最大元素、元素和。
一号操作,我们只用类似于平衡树“查询第K小”的方法就可以了。
二号,先将这个点找到(见一号操作),再旋到根。这样,修改操作就只会对根节点有影响了。
三号,找到第(L-1)个数的编号,旋到根。再找到第(R+1)个数,旋到根的右儿子。输出(R+1)个数的左儿子的元素和就行了(解释略)。
四号同三号。
五号,略同三号,在(R+1)个数的左儿子打一个Lazy标记,以后讨论到带标记的点就交换左右儿子并下放标记。由于偶数个标记不影响结果,建议使用异或修改标记。


代码

用的splay

#include 
#include 
#include 
using namespace std;
const int Q=100005;
int si[Q],ls[Q],rs[Q],f[Q],v[Q],maxn[Q],sum[Q],root=1,n,ch[Q]={0};
void lx(int x)
{
    int y=f[x],z=f[y];
    if(z)if(ls[z]==y)ls[z]=x;
        else rs[z]=x;
    rs[y]=ls[x];
    f[rs[y]]=y;
    f[x]=z;
    f[y]=x;
    ls[x]=y;
    si[x]=si[y];
    si[y]=si[ls[y]]+si[rs[y]]+1;
    maxn[x]=maxn[y];
    maxn[y]=max(max(maxn[ls[y]],maxn[rs[y]]),v[y]);
    sum[x]=sum[y];
    sum[y]=sum[ls[y]]+sum[rs[y]]+v[y];
}
void rx(int x)
{
    int y=f[x],z=f[y];
    if(z)if(ls[z]==y)ls[z]=x;
        else rs[z]=x;
    ls[y]=rs[x];
    f[ls[y]]=y;
    f[x]=z;
    f[y]=x;
    rs[x]=y;
    si[x]=si[y];
    si[y]=si[ls[y]]+si[rs[y]]+1;
    maxn[x]=maxn[y];
    maxn[y]=max(max(maxn[ls[y]],maxn[rs[y]]),v[y]);
    sum[x]=sum[y];
    sum[y]=sum[ls[y]]+sum[rs[y]]+v[y];
}
void pd(int x)
{
    swap(ls[x],rs[x]);
    if(ls[x])ch[ls[x]]^=1;
    if(rs[x])ch[rs[x]]^=1;
    ch[x]=0;
}
void splay(int x,int goal)
{

    while(f[x]!=goal&&f[x]!=0)
    {
        if(ch[x])pd(x);
        int y=f[x],z=f[y];
        if(z!=0&&z!=goal)
        {
            if(ch[z])pd(z);
            if(ch[y])pd(y);
            if(ls[z]==y)
                if(ls[y]==x)rx(y),rx(x);
                else lx(x),rx(x);
            else if(rs[y]==x)lx(y),lx(x);
                else rx(x),lx(x);
        }
        else
        {
            if(ch[y])pd(y);
            if(ls[y]==x)rx(x);
            else lx(x);
        }
    }
    if(f[x]==0)root=x;
}
int gk(int k)
{
    if(k==0)return 0;
    si[0]=0;
    int p=root;
    while(p)
    {
        if(ch[p])pd(p);
        if(si[ls[p]]+1==k)break;
        if(si[ls[p]]ls[p]]+1,p=rs[p];
        else p=ls[p];
    }
    return p;
}
int main()
{
    int i,x,m,y;
    scanf("%d%d",&n,&m);
    f[1]=0;
    for(i=1;i<=n+1;i++)
    {
        if(i<=n)scanf("%d",&v[i]);
        si[i]=1;
        maxn[i]=sum[i]=v[i];
        if(i==1)continue;
        x=root;
        while(x)
        {
            si[x]++;
            sum[x]+=v[i];
            maxn[x]=max(maxn[x],v[i]);
            if(i>x)if(rs[x])x=rs[x];
                else {rs[x]=i;break;}
            else if(ls[x])x=ls[x];
                else {ls[x]=i;break;}
        }
        f[i]=x;
        splay(i,0);
    }
    while(m--)
    {
        scanf("%d",&i);
        if(i==1)scanf("%d",&x),printf("%d\n",v[gk(x)]);
        if(i==2)
        {
            scanf("%d%d",&x,&y);
            x=gk(x);
            splay(x,0);
            v[x]+=y;
            sum[x]+=y;
            maxn[x]=max(maxn[x],v[x]);
        }
        if(i==3||i==4)
        {
            scanf("%d%d",&x,&y);
            x=gk(x-1),y=gk(y+1);
            if(x)splay(x,0);
            splay(y,x);
            if(i==3)printf("%d\n",sum[ls[y]]);
            else printf("%d\n",maxn[ls[y]]);
        }
        if(i==5)
        {
            scanf("%d%d",&x,&y);
            x=gk(x-1),y=gk(y+1);
            if(x)splay(x,0);
            splay(y,x);
            ch[ls[y]]^=1;
        }
    }
    return 0;
}

你可能感兴趣的:(平衡树)