Splay总结、模板

11 篇文章 0 订阅

我是来吹水总结的,不是来讲解的,如有想看讲解的,请去cty大神的博客

*以下的讲解全部以线段树例题最大值为题。

旋转有三种:

1.单旋,它的父亲就是根,那么直接旋上去即可。

2.它是父亲的左节点,父亲是父亲的父亲的左节点,或者反过来(都是右节点),我们当然可以把这个点连续单旋两次,但是,如果原本是一条链,旋完后还是一条链,这样做复杂度是很惨的,那我们先旋它的父亲,再旋它,这样树的层数就会减少。

3.它和父亲的位置关系,父亲和父亲的父亲的位置关系刚好相反(即左右或右左),连续单旋两次这个点即可。

在《最大值》里,每个节点表示的是它的子树的所有点所覆盖的区间的最大值。
Code:

void update(int x) {a[x] = max(b[x], max(a[t[x][0]], a[t[x][1]]));} //维护x节点代表的区间的最大值。
int lr(int x) {return t[fa[x]][1] == x;} //x是父亲的左节点返回0,否则返回1.
void rotate(int x) { //把x单旋至父亲那儿。
    int k = lr(x), y = fa[x];
    t[y][k] = t[x][!k];
    if(t[x][!k]) fa[t[x][!k]] = y;
    fa[x] = fa[y];
    if(fa[y]) t[fa[y]][lr(y)] = x;
    t[x][!k] = y; fa[y] = x;
    update(y); update(x);
}
void splay(int x, int y) { //表示把x旋到y的下面。
    while(fa[x] != y) {
        if(fa[fa[x]] != y)
            if(lr(fa[x]) == lr(x)) rotate(fa[x]); else rotate(x);
            rotate(x);
    }
}

这个板子还是很好理解的,就是需要把单旋记牢。
考场最好画个图出来然后按顺序打。
先上图(copy的):
这里写图片描述
以下描述以此图为准,至于另一种同理,具体实现见代码。
第一步:把Q的左节点设为P的右节点,P的右节点的父亲设为Q。
第二步:P的父亲设为Q的父亲
第三步:P的右节点设为Q
第四步:Q的父亲设为P
第五步:维护P,Q的值


基础操作讲完了,我们来讲稍微升级一点的。

1.单点修改

把x旋到树根,直接维护即可。

2.区间修改

假设我们要修改[a..b](a <= b)
先把a - 1旋到树根,再把b +1旋到树根的下面。
根据平衡树的定义,区间[a..b]就是根的右节点的左节点所代表的区间。
当然,我们也需要打标记(lazy)。
在执行把一个点旋到某地的操作时,要把它到根这一条链上的所有的点的标记都下传(自上(根)而下),这样旋转才有效。
之后在这个区间上乱搞。

3.删除区间

与区间修改类似,先旋转,然后把根的右节点指向左节点的指针赋值为NULL。

3.2删除单点

就是删除区间[a..a]

最大值区间修改标程:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define max(a, b) ((a) > (b) ? (a) : (b))
using namespace std;

const int N = 100005;

int n, Q, a[N], b[N], fa[N], t[N][2], lz[N], s[N];
void update(int x) {a[x] = max(b[x], max(a[t[x][0]], a[t[x][1]]));}
int lr(int x) {return t[fa[x]][1] == x;}
void chan(int x, int y) {if(x != 0) a[x] += y, b[x] += y, lz[x] += y;}
void down(int x) {if(lz[x]) chan(t[x][0], lz[x]), chan(t[x][1], lz[x]), lz[x] = 0;}
void xc(int x) {
    while(x) {s[++ s[0]] = x; x = fa[x];}
    for(; s[0]; s[0] --) down(s[s[0]]);
}
void rotate(int x) {
    int k = lr(x), y = fa[x];
    t[y][k] = t[x][!k];
    if(t[x][!k]) fa[t[x][!k]] = y;
    fa[x] = fa[y];
    if(fa[y]) t[fa[y]][lr(y)] = x;
    t[x][!k] = y; fa[y] = x;
    update(y); update(x);
}
void splay(int x, int y) {
    xc(x);
    while(fa[x] != y) {
        if(fa[fa[x]] != y)
            if(lr(fa[x]) == lr(x)) rotate(fa[x]); else rotate(x);
        rotate(x);
    }
}

void Init() {
    memset(a, -127, sizeof(a));
    scanf("%d", &n);
    fo(i, 1, n) {
        scanf("%d", &b[i + 1]);
        fa[i] = i + 1; t[i + 1][0] = i; update(i + 1);
    }
    fa[n + 1] = n + 2; t[n + 2][0] = n + 1; update(n + 2);
    for(scanf("%d", &Q); Q; Q --) {
        int x, y, z; scanf("%d %d %d", &z, &x, &y);
        x ++; y ++;
        splay(x - 1, 0); splay(y + 1, x - 1);
        if(z == 1) {
            int add; scanf("%d", &add);
            chan(t[y + 1][0], add);
        } else printf("%d\n", a[t[y + 1][0]]);
    }
}

int main() {
    freopen("a.in", "r", stdin);
    freopen("a.out", "w", stdout);
    Init(); 
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值