[Note]Splay学习笔记

开个坑记录一下学习Splay的历程。

Code

感谢rqy巨佬的代码,让我意识到了Splay可以有多短,以及我之前的Splay有多么的丑...

int fa[N], ch[N][2], rev[N], sz[N], n;

void upd(int x) { sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + 1; }
void pd(int x) {
    if (rev[x]) {
        std::swap(ch[x][0], ch[x][1]);
        rev[ch[x][0]] ^= 1;
        rev[ch[x][1]] ^= 1;
        rev[x] = 0;
    }
}
int dir(int x) { return x == ch[fa[x]][1]; }
void rot(int x) {
    int f = fa[x], d = dir(x);
    if (fa[x] = fa[f]) ch[fa[x]][dir(f)] = x;
    if (ch[f][d] = ch[x][d^1]) fa[ch[f][d]] = f;
    fa[ch[x][d^1] = f] = x;
    upd(f); upd(x);
}
int st[N];
void splay(int x, int t = 0) {
    int top = 0;
    for (int i = x; fa[i]; i = fa[i]) st[top++] = fa[i];
    while (top--) pd(st[top]);
    pd(x);
    for (; fa[x] != t; rot(x)) if (fa[fa[x]] != t) rot(dir(fa[x]) == dir(x) ? fa[x] : x);
}
int kth(int k, int t) {
    int o = t;
    while (1) {
        pd(o); 
        if (sz[ch[o][0]] == k-1) break;
        else if (sz[ch[o][0]] >= k) o = ch[o][0];
        else { k -= sz[ch[o][0]]+1; o = ch[o][1]; }
    }
    splay(o, fa[t]);
    return o; 
}
void reverse(int l, int r) {
    splay(1);
    int y = kth(r+1, 1);
    int x = ch[y][0];
    kth(l-1, x);
    rev[ch[ch[y][0]][1]] ^= 1;
}

(以上是维护序列翻转的splay,码量1k多点,唯一的缺陷是压行太严重,不是很好调)

Note

一些小地方

这个Splay的根是fa[i]=0的点,但是怎么找呢?不用管,直接splay(1),这样1就是根了啊(好暴力啊)。

建树怎么建啊?也不用动脑子啊,直接建成一条链就行了啊,反正之后还要再调整,一开始建的那么完美没有用啊。

关于标记

一般维护序列问题,需要支持几种区间修改操作就打几个标记。
不要忘了在splay前先pushdown所有祖先节点。

区间翻转

这个还是比较简单的,直接记录就行。

区间加

这个维护起来也不是很难,就是要注意标记下放的时候对维护的值的处理。

关于应该维护的值

这一部分也是十分的坑啊,只能多做题找感觉了。

区间最值

要注意0号点的值,不要傻乎乎的取最值的时候让0号点影响到答案。
要分开维护每个节点的值和其子树的最值,不要偷懒只存一个啊(我有一个下午都浪费在了这上面...)。

原文地址:https://www.cnblogs.com/wyxwyx/p/splay.html