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);
}
}
}