高大上的splay。
让我splay一下先。
好了,讲正事。(所有图片来自wiki,感谢大力支持)
splay,顾名思义,就是伸展树。它是二叉排序树的一种。然后,为了完成一些操作,它会通过不断旋转来在不破坏结构的情况下调整自身,从而减小深度,达到log n的均摊复杂度。可以说,splay的核心就是这O2(氧气)的旋转。
如何旋转?
首先,可以很容易的想到,左旋和右旋的打法。
但是,如果原来的树是一条链,这样从底层旋转上去之后,它依旧是一条链,所以我们还有以下两种旋转。
一字型旋转
如果x和x的父亲同是他们父亲的左/右节点,那么先旋转x的父亲,再旋转x。
之字形旋转
如果x和x的父亲属于他们父亲的不同儿子,那么就将x旋转两次。
附上代码
void rotate(int x) {
int y=f[x],z=son(x);f[x]=f[y];
if (f[x]) t[f[x]][son(y)]=x;
t[y][z]=t[x][1-z];
if (t[x][1-z]) f[t[x][1-z]]=y;
f[y]=x;t[x][1-z]=y;
}
void splay(int x,int y) {
while (f[x]!=y) {
if (f[f[x]]!=y)
if (son(f[x])==son(x)) rotate(f[x]);
else rotate(x);
rotate(x);
}
if (!y) root=x;
}
要注意旋转时候赋值的顺序,不然就爆炸了。(水好深)
而且,这个旋转不止可以把一个节点转到root,还可以把它转到任意一个节点的下面。(奥妙重zhong重zhong)
应用
一般来讲,splay可以很轻易地解决许多序列上的问题。
我们默认树的中序遍历为原序列。
那么对于区间[l~r],我们只需要把l-1旋转到树的root,再把r+1旋转到root的下面。
那么这样r+1的左子树就是要求的区间了。
对于询问,修改操作都可以这样解决。(打上lazy标记)
那么以上的只是实现了线段树可以实现的操作,那么其他的呢?
比如说,再区间的某个位置开始插入/删除/翻转tot个数。(NOI2005维护序列)
那么,splay就可以大展身手了。
我们只需要把那tot个数建成一棵splay,然后merge一下就好了。
删除也是,把段区间直接砍了。如果怕空间爆炸,开个回收栈玩一玩就好了。
翻转,就把左右子树交换就好了。
例题
找不到什么好题……(都没A)
简单写写。
用splay维护最大值,支持区间加。
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 200005
#define inf 0x7fffffff
using namespace std;
int n,m,x,y,z,root,tot,key[N],f[N],t[N][2],mx[N],d[N],add[N];
void updata(int x) {
mx[x]=max(key[x],max(mx[t[x][0]],mx[t[x][1]]));
}
void back(int v,int x) {
key[v]+=x;mx[v]+=x;add[v]+=x;
}
void clear(int x) {
if (add[x]) {
if (t[x][0]) back(t[x][0],add[x]);
if (t[x][1]) back(t[x][1],add[x]);
add[x]=0;
}
}
void remove(int x,int y) {
do {
d[++d[0]]=x;
x=f[x];
} while (x!=y);
while (d[0]) clear(d[d[0]--]);
}
int son(int x) {
if (x==t[f[x]][0]) return 0;else return 1;
}
void rotate(int x) {
int y=f[x],z=son(x);
t[y][z]=t[x][1-z];
if (t[x][1-z]) f[t[x][1-z]]=y;f[x]=f[y];
if (f[x]) t[f[x]][son(y)]=x;
f[y]=x;t[x][1-z]=y;
updata(y);updata(x);
}
void splay(int x,int y) {
remove(x,y);
while (f[x]!=y) {
if (f[f[x]]!=y)
if (son(f[x])==son(x)) rotate(f[x]);
else rotate(x);
rotate(x);
}
if (!y) root=x;
}
int main() {
scanf("%d",&n);
mx[0]=-inf;
fo(i,1,n) {
scanf("%d",&key[i+1]);f[i]=i+1;
t[i+1][0]=i;updata(i+1);
}
f[n+1]=root=n+2;t[n+2][0]=n+1;updata(n+2);
for(scanf("%d",&m);m;m--) {
scanf("%d",&z);
if (z==1) {
scanf("%d%d%d",&x,&y,&z);x++;y++;
splay(x-1,0);splay(y+1,x-1);
back(t[y+1][0],z);
} else {
scanf("%d%d",&x,&y);x++;y++;
splay(x-1,0);splay(y+1,x-1);
printf("%d\n",mx[t[y+1][0]]);
}
}
}