Splay与LCT学习笔记

Splay

1 : r o t a t e 1:rotate 1:rotate操作无论是左边旋到右边还是右边旋到左边,节点的相对高度都上升了。

2 : 2: 2:单旋 s p l a y splay splay可能会被卡掉,一条链单旋过后还是一条链。

3 : 3: 3:双旋分一下几种情况: ( x − > t o ) (x->to) (x>to)

  • t o to to x x x f a t h e r father father,此时直接把 x x x旋转上去就好。

  • x x x f a [ x ] fa[x] fa[x] f a [ f a [ x ] ] fa[fa[x]] fa[fa[x]]在同一条线上,此时先上旋 f a [ x ] fa[x] fa[x],再上旋 x x x

  • x x x f a [ x ] fa[x] fa[x] f a [ f a [ x ] ] fa[fa[x]] fa[fa[x]]不在同一条线上,此时把 x x x上旋两次就好了。

支持如下操作:

  • 加入权值为 v v v的点
  • 删除权值为 v v v的点
  • 查询排名为 x x x的数
  • 查询 x x x的排名
  • 查询 x x x的前驱
  • 查询 x x x的后继

f h q − t r e a p fhq-treap fhqtreap难写很多。。

一些注意的地方:

  • 因为我们双旋要涉及到爷节点,所以根应该还要有一个父亲节点 0 0 0。根的 f a t h e r father father是这个 0 0 0

  • 节点的编号是永不改变的,改变的只是节点在树中的相对位置。如果把 0 0 0节点视作超级根的话,根是可以改变的。

  • 注意我们 s p l a y splay splay的时候要把 t o to to设置为 f a t h e r [ t o ] father[to] father[to]。其原因是如果我们单纯判断 x x x != t o to to 的话是错误的。画个图可以知道,当 x x x被转到根的时候,原来的 t o to to早都不知道转到哪里去的。而 f a t h e r [ t o ] father[to] father[to]以上的结构是绝对不会变化的。换句话说, t o to to是可以在这次 s p l a y splay splay里变得,所以不能用 t o to to的任何信息来判断是否到头。要么事先存下来终点。

  • 每一次 r o t a t e rotate rotate之后,树的形态就改变了,所以要 p u s h u p ( k ) pushup(k) pushup(k)的节点信息。

  • 这题排名的意思应该是,如果没有重复,排名就是小于它的数的个数再减1,如果有重复。这个数会占用一段排名,但是认为最小的排名才代表这个数的排名。

#include <cstdio>
#include <iostream>
#define ls(x) (tr[x].ch[0])
#define rs(x) (tr[x].ch[1])
#define rt (tr[0].ch[1])
using namespace std;
const int N = 1e5 + 5, INF = 0x3f3f3f3f;
struct poi {
    int ch[2], siz, fa, cnt, v;
};
struct Splay {
    poi tr[N];
    int tot = 0;
    inline int newnode(int v, int fa) {
        tr[++tot].v = v;
        tr[tot].siz = tr[tot].cnt = 1;
        tr[tot].ch[0] = tr[tot].ch[1] = 0;
        tr[tot].fa = fa;
        return tot;
    }
    inline void connect(int x, int fa, int how) {
        tr[x].fa = fa;
        tr[fa].ch[how] = x;
    }
    inline int witch(int x) {
        return (tr[tr[x].fa].ch[1] == x);
    }
    inline void pushup(int k) {
        tr[k].siz = tr[ls(k)].siz + tr[rs(k)].siz + tr[k].cnt;
    }
    inline void rotate(int x) {
        int y = tr[x].fa, z = tr[y].fa;
        int wic = witch(x), wic2 = witch(y);
        connect(tr[x].ch[wic ^ 1], y, wic);
        connect(y, x, wic ^ 1);
        connect(x, z, wic2);
        pushup(y); pushup(x);
    }
    inline void splay(int x, int to) {// 为什么有人令to为to的父亲?
        to = tr[to].fa;// 为什么要是father呢?
        while (tr[x].fa != to) {
            //printf("%d %d\n", x, to);
            int y = tr[x].fa, z = tr[y].fa;
            int wic = witch(x), wic2 = witch(y);
            if (z == to) {rotate(x); return ;}
            else if (wic == wic2) {rotate(y); rotate(x);}
            else rotate(x), rotate(x);
        }
    }
    inline void ins(int v) {
        int cur = rt;
        if (!cur) { rt = newnode(v, 0); return ;} // a
        while (1) {
            tr[cur].siz++; //printf("cur = %d\n", cur);
            if (tr[cur].v == v) {tr[cur].cnt++; splay(cur, rt); return ;}// a
            int nxt = (v < tr[cur].v) ? 0 : 1;
            if (!tr[cur].ch[nxt]) {
                tr[cur].ch[nxt] = newnode(v, cur);
                splay(tr[cur].ch[nxt], rt);
                return ;
            }
            cur = tr[cur].ch[nxt];
        }
    }
    inline int find(int v) {
        int cur = rt;
        while (1) {
            if (!cur) return -1;
            if (tr[cur].v == v) {splay(cur, rt); return cur;}
            int nxt = v < tr[cur].v ? 0 : 1;
            cur = tr[cur].ch[nxt];
        }
    }
    inline void del(int v) {
        int cur = find(v);
        if (cur == -1) return ;
        if (tr[cur].cnt > 1){ tr[cur].siz--; tr[cur].cnt--; return ;}
        if (!tr[cur].ch[0] && !tr[cur].ch[1]) {
            rt = tot = 0;// ???
            return ;
        }
        else if (!tr[cur].ch[0]) {
            rt = tr[cur].ch[1];
            tr[rt].fa = 0;
            return ;
        }
        else {
            int nxt = tr[cur].ch[0];
            while (tr[nxt].ch[1]) nxt = tr[nxt].ch[1];
            splay(nxt, tr[cur].ch[0]);
            connect(tr[cur].ch[1], nxt, 1);
            connect(nxt, 0, 1);
            pushup(nxt);
        }
    }
    inline int rnk(int x) {
        int cur = rt, res = 0;
        while (1) {
            //printf("cur = %d curv = %d ls = %d rs = %d lsv = %d rsv = %d cnt = %d res = %d\n", cur, tr[cur].v, ls(cur), rs(cur), tr[ls(cur)].siz, tr[rs(cur)].siz, tr[cur].cnt, res);
            if (tr[cur].v == x) {res += tr[ls(cur)].siz + 1;splay(cur, rt); break;} //这里要先加再旋
            else if (tr[cur].v < x) {
                res += tr[ls(cur)].siz + tr[cur].cnt;
                cur = rs(cur);
            }
            else cur = ls(cur);
        }
        return res;
    }
    /*inline int atrnk(int c) {
        int cur = rt;
        while (1) {
            if (tr[ls(cur)].siz + 1 <= c && c <= tr[ls(cur)].siz + tr[cur].cnt) {splay(cur, rt); return tr[cur].v;}
            else if (tr[ls(cur)].siz + tr[cur].cnt < c) {
                c -= tr[ls(cur)].siz + tr[cur].cnt;
                cur = rs(cur);
            }
            else c = ls(cur);
        }
    }*/
    inline int atrnk(int c) {
        int cur = rt;
        while (1) {
            int used = tr[cur].siz - tr[ tr[cur].ch[1] ].siz;
            if (c > tr[ tr[cur].ch[0]].siz && c <= used) break;
            if (c < used) cur = tr[cur].ch[0];
            else {
                c -= used;
                cur = tr[cur].ch[1];
            }
        }
        splay(cur, rt);
        return tr[cur].v;
    }
    inline int pre(int x) {
        int cur = rt, ans = -INF;
        while (1) {
            if (!cur) return ans;
            if (tr[cur].v < x) {
                ans = max(ans, tr[cur].v);
                cur = rs(cur);
            }
            else cur = ls(cur);
        }
    }
    inline int suf(int x) {
        int cur = rt, ans = INF;
        while (1) {
            if (!cur) return ans;
            if (tr[cur].v > x) {
                ans = min(ans, tr[cur].v);
                cur = ls(cur);
            }
            else cur = rs(cur);
        }
    }
}T1;
inline int read() {
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-')f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int n;
int main() {
    //freopen("data.in", "r", stdin);
    //freopen("data.out", "w", stdout);
    n = read();
    while (n--) {
        int opt = read(), x = read();
        switch (opt) {
            case 1: {T1.ins(x); break;}
            case 2: {T1.del(x); break;}
            case 3: {printf("%d\n", T1.rnk(x)); break;}
            case 4: {printf("%d\n", T1.atrnk(x)); break;}
            case 5: {printf("%d\n", T1.pre(x)); break;}
            case 6: {printf("%d\n", T1.suf(x)); break;}
        }
        //puts("kk");
    }
    return 0;
}


s p l a y splay splay这玩意可能除了写写 L C T LCT LCT之外我不打算用它做任何的事情,难写且易错。

不如 f h q − t r e a p fhq-treap fhqtreap更加实用于考场!!!

LCT

LCT只需要用到 s p l a y splay splay的两个核心操作。 r o t a t e rotate rotate s p l a y splay splay,且这里的 s p l a y splay splay还是旋转到这个树的根的。

LCT函数解析:
pushup(k)

这是为了统计 s p l a y splay splay子树内的信息,说是子树。不过你后面 s p l i t split split过后,只有一条链会在这个子树里面了。所以实际上处理的就是链的信息。

inline void pushup(int k) {
        tr[k].v = tr[tr[k].ch[0]].v ^ tr[tr[k].ch[1]].v ^ val[k];
}
pushdown(k)

下传标记。我的写法是一个点如果有 t a g tag tag,说明它还没有被翻转。(和线段树写法稍微有些不同)

inline void pushdown(int x) { // 自己还未转过
        if (tr[x].rev) {
            tr[x].rev ^= 1;
            swap(tr[x].ch[0], tr[x].ch[1]);
            tr[tr[x].ch[0]].rev ^= 1;
            tr[tr[x].ch[1]].rev ^= 1;
        }
}
rotate
inline void rotate(int x) {
        int y = tr[x].fa, z = tr[y].fa, wic1 = witch(x), wic2 = witch(y), w = tr[x].ch[wic1 ^ 1];
        if (notrt(y)) tr[z].ch[wic2] = x; tr[y].ch[wic1] = w; tr[x].ch[wic1 ^ 1] = y;
        if (w) tr[w].fa = y; tr[x].fa = z; tr[y].fa = x;
        pushup(y);
}

这里就是仿照 s p l a y splay splay r o t a t e rotate rotate,不过千万要判断 y y y是不是这个 s p l a y splay splay的根。否则就有可能沟通了若干个 s p l a y splay splay导致错误。

splay
inline void splay(int x) {
        int y = x; top = 0;
        a[++top] = y;
        while (notrt(y)) a[++top] = y = tr[y].fa;
        while (top) pushdown(a[top--]);
        while (notrt(x)) {
            //printf("splay : x = %d\n", x);
            int y = tr[x].fa, z = tr[y].fa;
            int wic1 = witch(x), wic2 = witch(y);
            if (notrt(y))  rotate(wic1 == wic2 ? y : x);
            rotate(x);
        }
        pushup(x);
}

这里要注意标记的处理。下传标记一定要是自上而下传的,所以要用个栈来存下。

access

沟通 x x x和当前的根。使得这条根上面全是重路径。由于LCT认父不认子的性质,一个点 x x x f a fa fa是可以和它不在一个 s p l a y splay splay中的。

inline void access(int x) {
        int y = 0;
        do {
            //printf("x = %d fa = %d\n", x, tr[x].fa);
            splay(x); tr[x].ch[1] = y; pushup(x); y = x; x = tr[x].fa;
        } while(x);
}
remov

强制转换 s p l a y splay splay里这个点的左右孩子关系

inline void remov(int x) {
        swap(tr[x].ch[0], tr[x].ch[1]);
        tr[tr[x].ch[0]].rev ^= 1;
        tr[tr[x].ch[1]].rev ^= 1;
}
makert

x x x变为这个树的根。需要先打通路径,再转到 s p l a y splay splay的根。然后再往下翻转。这里注意标记的处理。因为原来树的根的 d e p dep dep是最小的,所以我们期望的 s p l a y splay splay是点 x x x只有右子树的一条链。这里左右孩子交换就把左子树交换到右子树去了,就可以让 x x x深度最小。实现很简单。

inline void makert(int x) {
        access(x); splay(x); remov(x);
}
split(x,y)

x − y x-y xy这条路径上提出来一个 s p l a y splay splay,并使得 y y y是根。

inline void split(int x, int y) {
        makert(x); access(y); splay(y);
}
findrt(x)

找到 x x x所在 s p l a y splay splay的根节点。 x x x转到 s p l a y splay splay的根,然后一直往左子树走就好。注意这里操作完成之后,原来的 x x x就是 s p l a y splay splay的新根了。

inline int findrt(int x) {
        access(x); splay(x);
        while (tr[x].ch[0]) pushdown(x), x = tr[x].ch[0];
        return x;
}
link(x,y)

连接 x , y x,y x,y这条边,在 s p l a y splay splay上保证 y y y是根节点。(我们 f i n d r t findrt findrt之后, s p l a y splay splay的根节点就是 y y y了,所以 x x x得父亲要是 y y y

inline void link(int x, int y) {
        makert(x);
        if (findrt(y) != x) tr[x].fa = y;
}
cut(x,y)

删除 x , y x,y x,y这条边。因为 x , y x,y x,y的距离为1,所以在 s p l a y splay splay上它们是相邻的两个点,需要判断不合法的情况。

inline void cut(int x, int y) {
        makert(x);
        if (findrt(y) != x || tr[x].fa != y || tr[x].ch[1]) return ;
        tr[x].fa = tr[y].ch[0] = 0;
        pushup(y);
}

f i n d r t ( y ) ! = x findrt(y)!=x findrt(y)!=x是用来判断在不在一个 s p l a y splay splay里面。
t r [ x ] . f a ! = y 和 t r [ x ] . c h [ 1 ] tr[x].fa!=y和tr[x].ch[1] tr[x].fa!=ytr[x].ch[1]是用来判断 x x x y y y的两个点距离是不是1。
考虑如果不是1的话,至少有一个条件会被满足。

注意修改 x x x的时候,要先把 x x x旋转到根再修改。是为了改变它的孩子的信息。从根往下下传标记。而且这样改不需要修改 x x x所在 s p l a y splay splay的其他的根。

完整代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
inline int read() {
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-')f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
int n, m, val[N], top, a[N];
struct LCT {
    struct poi {
        int ch[2], fa, v, rev;
    }tr[N];
    inline void pushup(int k) {tr[k].v = tr[tr[k].ch[0]].v ^ tr[tr[k].ch[1]].v ^ val[k];}
    inline void pushdown(int k) {
        if (tr[k].rev) {
            tr[k].rev = 0;
            swap(tr[k].ch[0], tr[k].ch[1]);
            tr[tr[k].ch[0]].rev ^= 1;
            tr[tr[k].ch[1]].rev ^= 1;
        }
    }
    inline int witch(int x) {return (tr[tr[x].fa].ch[1] == x);}
    inline int notrt(int x) {return (tr[tr[x].fa].ch[0] == x || tr[tr[x].fa].ch[1] == x);}
    inline void rotate(int x) {
        int y = tr[x].fa, z = tr[y].fa, wic1 = witch(x), wic2 = witch(y), w = tr[x].ch[wic1 ^ 1];
        if (notrt(y)) tr[z].ch[wic2] = x; tr[x].ch[wic1 ^ 1] = y; tr[y].ch[wic1] = w;
        if (w) tr[w].fa = y; tr[x].fa = z; tr[y].fa = x;
        pushup(y);
    }
    inline void splay(int x) {
        int y = x; top = 0;
        a[++top] = y;
        while (notrt(y)) a[++top] = y = tr[y].fa;
        while (top) pushdown(a[top--]);
        while (notrt(x)) {
            int y = tr[x].fa, z = tr[y].fa;
            int wic1 = witch(x), wic2 = witch(y);
            if (notrt(y)) rotate(wic1 == wic2 ? y : x);
            rotate(x);
        }
        pushup(x);
    }
    inline void access(int x) {
        int y = 0;
        do {
            splay(x); tr[x].ch[1] = y; pushup(x); tr[y].fa = x; y = x; x = tr[x].fa; // ???
        } while (x);
    }
    inline void remov(int x) {
        swap(tr[x].ch[0], tr[x].ch[1]);
        tr[tr[x].ch[0]].rev ^= 1;
        tr[tr[x].ch[1]].rev ^= 1;
    }
    inline void makert(int x) {
        access(x); splay(x); remov(x);
    }
    inline void split(int x, int y) {
        makert(x); access(y); splay(y);
    }
    inline int getrt(int x) {
        access(x); splay(x); // access
        while (tr[x].ch[0]) pushdown(x), x = tr[x].ch[0];
        return x;
    }
    inline void link(int x, int y) {
        makert(x);
        if (getrt(y) != x) tr[x].fa = y;
        pushup(y); // ???
    }
    inline void cut(int x, int y) {
        makert(x);
        if (getrt(y) != x || tr[x].ch[1] || tr[x].fa != y) return ;
        tr[x].fa = tr[y].ch[0] = 0;
        pushup(y);
    }
}T;
int main() {
    n = read(); m = read();
    for (int i = 1; i <= n; ++i) val[i] = read();
    for (int i = 1; i <= m; ++i) {
        int opt = read(), x = read(), y = read();
        if (opt == 0) {T.split(x, y); printf("%d\n", T.tr[y].v);}
        if (opt == 1) T.link(x, y);
        if (opt == 2) T.cut(x, y);
        if (opt == 3) {T.splay(x); val[x] = y;}
    }
    return 0;
}



助记:一旦涉及到孩子变化的,都要pushup根节点。split,link,cut都实现要选定好一个点为根。split因为要把这个路径splay割出来所以要access。getrt(x)之后x这个点就到根了。无他,唯手熟尔。。。

注意上面这玩意维护的是一个森林,它不是一个图。它保证时时刻刻只是树边。它是动态树连通性而不是tm的动态图连通性。在线的动态图连通性比较复杂。。。

bzoj2002

每个点朝他的后继节点连边,跳出去的就建立一个虚拟点都连向他。就要支持加边,删边,维护点到这个虚拟点的距离。考虑每个点最多只有一个后继节点,把它反过来看就是一个严格树的形态。就是LCT了吧。

距离这个东西还是维护一个像splay的子树和的东西解决。考虑最后问的形态一定是一个链。 注意这个dep表示的是一个点到它下边的最深孩子的节点个数,减一就是答案了。

luoguP2147

这题才是LCT维护动态树连通性的裸题啊哈哈哈。直接以一个点为根,getfa一下就好了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值