splay学习小记

高大上的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]]);
        }
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值