Splay与LCT学习笔记

本文深入探讨了Splay树的基本操作与应用,包括旋转、查找、插入、删除等,以及如何通过Splay树实现LCT(Link-Cut Tree)。详细解析了LCT的各种函数,如pushup、pushdown、rotate、splay、access、remov、makert、split、findrt、link和cut,展示了如何利用LCT维护动态树连通性和处理动态图连通性问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Splay

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

2:2:2:单旋splaysplaysplay可能会被卡掉,一条链单旋过后还是一条链。

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

  • tototoxxxfatherfatherfather,此时直接把xxx旋转上去就好。

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

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

支持如下操作:

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

fhq−treapfhq-treapfhqtreap难写很多。。

一些注意的地方:

  • 因为我们双旋要涉及到爷节点,所以根应该还要有一个父亲节点000。根的fatherfatherfather是这个000

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

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

  • 每一次rotaterotaterotate之后,树的形态就改变了,所以要pushup(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;
}


splaysplaysplay这玩意可能除了写写LCTLCTLCT之外我不打算用它做任何的事情,难写且易错。

不如fhq−treapfhq-treapfhqtreap更加实用于考场!!!

LCT

LCT只需要用到splaysplaysplay的两个核心操作。rotaterotaterotatesplaysplaysplay,且这里的splaysplaysplay还是旋转到这个树的根的。

LCT函数解析:
pushup(k)

这是为了统计splaysplaysplay子树内的信息,说是子树。不过你后面splitsplitsplit过后,只有一条链会在这个子树里面了。所以实际上处理的就是链的信息。

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

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

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);
}

这里就是仿照splaysplaysplayrotaterotaterotate,不过千万要判断yyy是不是这个splaysplaysplay的根。否则就有可能沟通了若干个splaysplaysplay导致错误。

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

沟通xxx和当前的根。使得这条根上面全是重路径。由于LCT认父不认子的性质,一个点xxxfafafa是可以和它不在一个splaysplaysplay中的。

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

强制转换splaysplaysplay里这个点的左右孩子关系

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

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

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

x−yx-yxy这条路径上提出来一个splaysplaysplay,并使得yyy是根。

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

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

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,yx,yx,y这条边,在splaysplaysplay上保证yyy是根节点。(我们findrtfindrtfindrt之后,splaysplaysplay的根节点就是yyy了,所以xxx得父亲要是yyy

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

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

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);
}

findrt(y)!=xfindrt(y)!=xfindrt(y)!=x是用来判断在不在一个splaysplaysplay里面。
tr[x].fa!=y和tr[x].ch[1]tr[x].fa!=y和tr[x].ch[1]tr[x].fa!=ytr[x].ch[1]是用来判断xxxyyy的两个点距离是不是1。
考虑如果不是1的话,至少有一个条件会被满足。

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

完整代码:

#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、付费专栏及课程。

余额充值