这是一道非常好的题目,考察对于splay(或其他平衡树)的综合应用,需要注意的是splay 上浮 和 下沉 的实现,我的splay以自顶向下方式实现。
#include <iostream>
#include <cstdio>
using namespace std;
const int inf=~0U>>2;
int a[200010],lazy[200010],mi[200010],size[200010];
bool rev[200010];
int l[200010],r[200010];
int pl[200010],pr[200010];
int n,m,i,root,x,y,z;
void add(int x,int y)//给x为根的子树添加大小为y的增量标记
{
if(!x||!y) return;
lazy[x]+=y;
a[x]+=y;
mi[x]+=y;
}
inline void push_down(int x)
{
if(!x) return;
add(l[x],lazy[x]);
add(r[x],lazy[x]);
if(rev[x])
{
swap(l[x],r[x]);
if(l[x]) rev[l[x]]^=1;
if(r[x]) rev[r[x]]^=1;
}
rev[x]=lazy[x]=0;
}
inline void update(int x)
{
size[x]=size[l[x]]+size[r[x]]+1;
mi[x]=min(a[x],min(mi[l[x]],mi[r[x]]));
}
void zig(int &x)
{
int rc=r[x];
r[x]=l[rc];
l[rc]=x;
update(x);
x=rc;
}
void zag(int &x)
{
int lc=l[x];
l[x]=r[lc];
r[lc]=x;
update(x);
x=lc;
}
void splay(int &x,int y)//在以x为根的子树中寻找第y个数,并将其伸展到该子树的根
{
if(!x) return;
pl[0]=pr[0]=0;//左树和右树
for(;;)
{
push_down(x);
push_down(l[x]);
push_down(r[x]);//下传标记
int temp=size[l[x]]+1;
if(y==temp||(y<temp&&!l[x])||(y>temp&&!r[x]))
break;//已经找到
if(y<temp)//在左子树
{
if(l[l[x]]&&y<=size[l[l[x]]])
zag(x);
pr[++pr[0]]=x;
x=l[x];//连接到左树
}
else//在右子树
{
y-=temp;
temp=size[l[r[x]]]+1;
if(r[r[x]]&&y>temp)
{
y-=temp;
zig(x);
}
pl[++pl[0]]=x;
x=r[x];//连接到右树
}
}
pl[++pl[0]]=l[x];
pr[++pr[0]]=r[x];//组合左中右树
for(int i=pl[0]-1;i>0;i--)
{
r[pl[i]]=pl[i+1];
update(pl[i]);
}//从下往上更新左树信息
for(int i=pr[0]-1;i>0;i--)
{
l[pr[i]]=pr[i+1];
update(pr[i]);
}//从下往上更新右树信息
l[x]=pl[1];
r[x]=pr[1];
update(x);//组合左中右树,更新根信息
}
void ADD(int x,int y,int z)//增加操作,伸展y+1到根、x-1到根的左子节点,则x-1的右子树就代表[x,y],对其添加增量标记即可
{
splay(root,y+1);
splay(l[root],x-1);
add(r[l[root]],z);
}
void INSERT(int x,int y)//插入操作, 伸展x到根,在x和x的右子节点之间插入新节点y
{
splay(root,x);
a[++n]=y;
r[n]=r[root];
r[root]=n;
update(n);
update(root);
}
void DELETE(int x)//删除操作,伸展x到根、x+1到根的右子节点,直接将x+1作为新根
{
splay(root,x);
splay(r[root],1);
l[r[root]]=l[root];
root=r[root];
update(root);
}
void REVERSE(int x,int y)//翻转操作,伸展y+1到根、x-1到根的左子节点,则x-1的右子树就代表[x,y],对其添加翻转标记即可
{
splay(root,y+1);
splay(l[root],x-1);
rev[r[l[root]]]^=1;
}
void REVOLVE(int x,int y,int z)//滚动操作,相当于交换区间[a,b]和[b+1,c]
{
z%=y-x+1;
if(!z) return;
int mid=y-z;
splay(root,mid);
splay(l[root],x-1);
splay(r[root],y-size[l[root]]);
z=l[root];
l[root]=r[z];
r[z]=l[r[root]];
l[r[root]]=0;
update(z);
update(r[root]);
update(root);
splay(root,1);
l[root]=z;
update(root);
}
void MIN(int x,int y)//求最小值,伸展y+1到根、x-1到根的左子节点,则x-1的右子树就代表[x,y],输出其最小值即可
{
splay(root,y+1);
splay(l[root],x-1);
printf("%d\n",mi[r[l[root]]]);
}
int main()
{
char str[10];
cin>>n;
size[1]=1;
l[n+2]=n+1;
size[n+2]=n+2; //初始化,1为极小点,n+2为极大点防止越界
mi[1]=mi[n+2]=mi[1]=mi[0]=inf;
for(i=2;i<=n+1;i++)//a[i+1]代表位置i
{
scanf("%d",&a[i]);
l[i]=i-1;
size[i]=i;
mi[i]=min(mi[i-1],a[i]);
}
mi[n+2]=mi[n+1];
root=n=n+2;//根
cin>>m;
for(i=0;i<m;i++)
{
scanf("%s",&str);
if(str[0]=='A')
{
scanf("%d%d%d",&x,&y,&z);
ADD(++x,++y,z);
}
if(str[0]=='I')
{
scanf("%d%d",&x,&y);
INSERT(++x,y);
}
if(str[0]=='D')
{
scanf("%d",&x);
DELETE(++x);
}
if(str[0]=='M')
{
scanf("%d%d",&x,&y);
MIN(++x,++y);
}
if(str[0]=='R'&&str[3]=='E')
{
scanf("%d%d",&x,&y);
REVERSE(++x,++y);
}
if(str[0]=='R'&&str[3]=='O')
{
scanf("%d%d%d",&x,&y,&z);
REVOLVE(++x,++y,z);
}
}
return 0;
}