P3690 【模板】Link Cut Tree (动态树lct)

学习资源

https://www.cnblogs.com/flashhu/p/8324551.html

中序遍历Splay得到的每个点的深度序列严格递增

时间复杂度 O ( ( n + m ) log ⁡ n ) O((n+m)\log n) O((n+m)logn)


那些年我用lct做过的题目:


数组版代码

namespace LCT { // lct板子
    const ll mod = 51061;
    ll sum[N];
    ll w[N];// w[x]表示x的点权

	int h[N];// 缩点后x所在的点 类似 并查集里的father[]

    int sz[N];//sz[x] 表示以x为根的splay树的大小
    int rev[N];//rev为区间翻转懒标记数组
    int f[N];// 父节点
    int st[N];//栈
    int c[N][2];// 每个节点的两个儿子

    // 判断节点x是否为一个splay的根 如果不是根 返回true
    inline bool nRoot(register int x) {
        // 如果节点x是根 则其与其父亲节点之间连的是轻边
        // 如果连的是轻边 他的父亲的儿子里没有它
        return c[f[x]][0] == x || c[f[x]][1] == x;
    }

#define lc c[x][0]
#define rc c[x][1]

    // splay区间翻转操作
    inline void reverse(register int x) {
        swap(lc, rc);
        rev[x] ^= 1;
    }

    // 判断并释放懒标记
    inline void pushDown(register int x) {
        if (rev[x]) {
            swap(lc, rc);
            rev[lc] ^= 1, rev[rc] ^= 1;
            rev[x] = 0;
        }
    }

    // 更新信息
    inline void pushUp(register int x) {
        sum[x] = (sum[lc] + sum[rc] + w[x]) % mod;
        sz[x] = sz[lc] + sz[rc] + 1;
    }

    // 一次旋转
    inline void rotate(register int x) {
        register int y = f[x], z = f[y], k = (c[y][1] == x), w = c[x][!k];
        if (nRoot(y))
            c[z][c[z][1] == y] = x;
        c[x][!k] = y;
        c[y][k] = w;

        if (w)
            f[w] = y;
        f[y] = x;
        f[x] = z;
        pushUp(y);//原来的父亲现在是儿子了 更新新儿子的信息 而新父亲的信息不是在这里更新的
    }

    // 旋转一颗splay树 改为以x为根节点
    inline void splay(register int x) {
        register int y = x, z = 0;
        st[++z] = y;//暂存当前点到根的整条路径 pushdown时一定要从上往下放标记
        while (nRoot(y)) st[++z] = y = f[y];
        while (z) pushDown(st[z--]);
        //然后再从下往上旋转 splay的基本操作
        while (nRoot(x)) {
            y = f[x];
            z = f[y];
            if (nRoot(y))
                rotate((c[y][0] == x) ^ (c[z][0] == y) ? x : y);
            rotate(x);
        }
        pushUp(x);//更新最终新父亲x的信息
    }
	
	// 并查集find
	inline int geth(register int x) {
        return x == h[x] ? x : h[x] = geth(h[x]);
    }

    // 打通当前根节点到指定节点的树链
    // 使得一条中序遍历以根开始、以指定点结束的splay出现
    inline void access(register int x) {
    	//for (int y = 0; x; y = x, x = f[x] = geth(f[x])) { // merge时使用
        for (int y = 0; x; x = f[y = x]) {
            splay(x);
            rc = y;
            pushUp(x);
        }
    }

    //换根
    inline void makeRoot(register int x) {
        access(x);
        splay(x);
        reverse(x);
    }

    //提取路径 拉出x->y的路径成为一个splay 然后以y为splay的根
    inline void split(register int x, register int y) {
        makeRoot(x);
        access(y);
        splay(y);
    }

    // 查找在树的根
    // 因为lct要求满足中序遍历Splay得到的每个点的深度序列严格递增 根的深度最小
    // 一直往左儿子就是了
    int findRoot(register int x) {
        access(x);
        splay(x);
        while (lc) {
            pushDown(x);
            x = lc;
        }
        // 现在x是root
        splay(x);
        return x;
    }

    // 连边
    inline void link(register int x, register int y) {
        makeRoot(x);
        if (findRoot(y) != x)
            f[x] = y;
    }

    // 断边
    inline void cut(register int x, register int y) {
        makeRoot(x);
        if (findRoot(y) == x && f[y] == x && !c[y][0]) {
            f[y] = c[x][1] = 0;
            pushUp(x);
        }
    }

    // 判断 x 和 y 是否连通
    bool isConnected(int x, int y) {
        return findRoot(x) == findRoot(y);
    }

	// 缩点
    inline void del(register int x, register int y) {
        if (x) { // 如果点存在
            h[x] = y;
            del(lc, y);
            del(rc, y);
        }
    }


    //双连通分量存在时 连接x和y
    inline void merge(register int x, register int y) {
        if (x == y) return;
        makeRoot(x);
        if (findRoot(y) != x) {
            f[x] = y;// 等于 link(x,y)
            return;
        }
        // 双连通分量 暴力缩点
        del(rc, x);// 将以x为根的splay树缩成一个点 都用x来表示
        rc = 0;
        pushUp(x);//缩点 删点
    }

    void init(int n) {
        for (int i = 0; i <= n; i++) {
            f[i] = c[i][0] = c[i][1] = 0;
            w[i] = 1;
            h[i] = i;
        }
    }
}
using namespace LCT;

P3690 【模板】Link Cut Tree (动态树)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m, k;

namespace LCT { // lct板子
    int v[N];// 点权
    int f[N];// 父节点
    int st[N];//栈
    int c[N][2];// 每个节点的两个儿子

    // 判断节点x是否为一个splay的根 如果不是根 返回true
    inline bool nRoot(register int x) {
        // 如果节点x是根 则其与其父亲节点之间连的是轻边
        // 如果连的是轻边 他的父亲的儿子里没有它
        return c[f[x]][0] == x || c[f[x]][1] == x;
    }

    int rev[N];//rev为区间翻转懒标记数组
    
#define lc c[x][0]
#define rc c[x][1]

    // splay区间翻转操作
    inline void reverse(register int x) {
        swap(lc,rc);
        rev[x] ^= 1;
    }

    // 判断并释放懒标记
    inline void pushDown(register int x) {
        if (rev[x]) {
            if (lc) reverse(lc);
            if (rc) reverse(rc);
            rev[x] = 0;
        }
    }

    int a[N];// a[x]表示 以x为根的splay树在中序遍历下形成的区间 的异或总和

    // 更新信息
    inline void pushUp(register int x) {
        a[x] = a[lc] ^ a[rc] ^ v[x];
    }

    // 一次旋转
    inline void rotate(register int x) {
        register int y = f[x], z = f[y], k = (c[y][1] == x), w = c[x][!k];
        if (nRoot(y))
            c[z][c[z][1] == y] = x;
        c[x][!k] = y;
        c[y][k] = w;

        if (w)
            f[w] = y;
        f[y] = x;
        f[x] = z;
        pushUp(y);//原来的父亲现在是儿子了 更新新儿子的信息 而新父亲的信息不是在这里更新的
    }

    // 旋转一颗splay树 改为以x为根节点
    inline void splay(register int x) {
        register int y = x, z = 0;
        st[++z] = y;//暂存当前点到根的整条路径 pushdown时一定要从上往下放标记
        while (nRoot(y)) st[++z] = y = f[y];
        while (z) pushDown(st[z--]);
        //然后再从下往上旋转 splay的基本操作
        while (nRoot(x)) {
            y = f[x];
            z = f[y];
            if (nRoot(y))
                rotate((c[y][0] == x) ^ (c[z][0] == y) ? x : y);
            rotate(x);
        }
        pushUp(x);//更新最终新父亲x的信息
    }

    // 打通当前根节点到指定节点的树链
    // 使得一条中序遍历以根开始、以指定点结束的splay出现
    inline void access(register int x) {
        for (int y = 0; x; y = x, x = f[x]) {
            splay(x);
            rc = y;
            pushUp(x);
        }
    }

    //换根
    inline void makeRoot(register int x) {
        access(x);
        splay(x);
        reverse(x);
    }

    //提取路径 拉出x->y的路径成为一个splay 然后以y为splay的根
    inline void split(register int x, register int y) {
        makeRoot(x);
        access(y);
        splay(y);
    }

    // 查找在树的根
    // 因为lct要求满足中序遍历Splay得到的每个点的深度序列严格递增 根的深度最小
    // 一直往左儿子就是了
    int findRoot(register int x) {
        access(x);
        splay(x);
        while (lc) {
            pushDown(x);
            x = lc;
        }
        // 现在x是root
        splay(x);
        return x;
    }

    // 连边
    inline void link(register int x, register int y) {
        makeRoot(x);
        if (findRoot(y) != x) {
            f[x] = y;
        };
    }

    // 断边
    inline void cut(register int x, register int y) {
        makeRoot(x);
        if (findRoot(y) == x && f[y] == x && !c[y][0]) {
            f[y] = c[x][1] = 0;
            pushUp(x);
        }
    }
}
using namespace LCT;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> m; // n个点 m个操作
    for (int i = 1; i <= n; i++) {
        cin >> v[i]; // 每个点的点权
    }
    int op, x, y;
    while (m--) {
        cin >> op >> x >> y;
        if (op == 0) { // 询问 (x->y)所有点权xor和
            split(x, y);
            //此时y为splay树的根 y是树中中序遍历到的第一个点 x是中序遍历到的最后一个点
            printf("%d\n", a[y]);

        } else if (op == 1) { //连通x->y
            link(x, y);

        } else if (op == 2) { // 删除x->y这条边
            cut(x, y);

        } else {//op=3 将x点权变为y
            // 每次修改值的时候 需要将x转上来
            splay(x);
            v[x] = y;
        }
    }
    return 0;
}

P1501 [国家集训队]Tree II

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m, k;
typedef long long ll ;
namespace LCT { // lct板子
    const int mod = 51061;
    ll sum[N];// sum[x] 表示x所表示的区间的和
    ll add[N], mul[N];// 区间懒标记 表示还未下放的数
    ll w[N];// w[x]表示x的点权

    int sz[N];//sz[x] 表示以x为根的splay树的大小
    int rev[N];//rev为区间翻转懒标记数组
    int f[N];// 父节点
    int st[N];//栈
    int c[N][2];// 每个节点的两个儿子

    // 判断节点x是否为一个splay的根 如果不是根 返回true
    inline bool nRoot(register int x) {
        // 如果节点x是根 则其与其父亲节点之间连的是轻边
        // 如果连的是轻边 他的父亲的儿子里没有它
        return c[f[x]][0] == x || c[f[x]][1] == x;
    }

#define lc c[x][0]
#define rc c[x][1]

    // splay区间翻转操作
    inline void reverse(register int x) {
        swap(lc,rc);
        rev[x] ^= 1;
    }

    // 给x所代表的区间都加上一个值ad
    void Add(int x, int ad) {
        add[x] = (add[x] + ad) % mod;
        sum[x] = (sum[x] + sz[x] * ad % mod) % mod;
        w[x] = (w[x] + ad) % mod;
    }

    // 给x所代表的区间都乘上一个值mu
    void Mul(int x, int mu) {
        mul[x] = mul[x] * mu % mod;
        add[x] = add[x] * mu % mod;
        sum[x] = sum[x] * mu % mod;
        w[x] = w[x] * mu % mod;
    }

    // 判断并释放懒标记
    inline void pushDown(register int x) {
        if (rev[x]) {
            if (lc) reverse(lc);
            if (rc) reverse(rc);
            rev[x] = 0;
        }

        if (mul[x] != 1) {
            if (lc) Mul(lc, mul[x]);
            if (rc) Mul(rc, mul[x]);
            mul[x] = 1;
        }
        if (add[x]) {
            if (lc) Add(lc, add[x]);
            if (rc) Add(rc, add[x]);
            add[x] = 0;
        }
    }

    // 更新信息
    inline void pushUp(register int x) {
        sum[x] = (sum[lc] + sum[rc] + w[x]) % mod;
        sz[x] = sz[lc] + sz[rc] + 1;
    }

    // 一次旋转
    inline void rotate(register int x) {
        register int y = f[x], z = f[y], k = (c[y][1] == x), w = c[x][!k];
        if (nRoot(y))
            c[z][c[z][1] == y] = x;
        c[x][!k] = y;
        c[y][k] = w;

        if (w)
            f[w] = y;
        f[y] = x;
        f[x] = z;
        pushUp(y);//原来的父亲现在是儿子了 更新新儿子的信息 而新父亲的信息不是在这里更新的
    }

    // 旋转一颗splay树 改为以x为根节点
    inline void splay(register int x) {
        register int y = x, z = 0;
        st[++z] = y;//暂存当前点到根的整条路径 pushdown时一定要从上往下放标记
        while (nRoot(y)) st[++z] = y = f[y];
        while (z) pushDown(st[z--]);
        //然后再从下往上旋转 splay的基本操作
        while (nRoot(x)) {
            y = f[x];
            z = f[y];
            if (nRoot(y))
                rotate((c[y][0] == x) ^ (c[z][0] == y) ? x : y);
            rotate(x);
        }
        pushUp(x);//更新最终新父亲x的信息
    }


    // 打通当前根节点到指定节点的树链
    // 使得一条中序遍历以根开始、以指定点结束的splay出现
    inline void access(register int x) {
        for (int y = 0; x; x = f[y = x]) {
            splay(x);
            rc = y;
            pushUp(x);
        }
    }

    //换根
    inline void makeRoot(register int x) {
        access(x);
        splay(x);
        reverse(x);
    }

    //提取路径 拉出x->y的路径成为一个splay 然后以y为splay的根
    inline void split(register int x, register int y) {
        makeRoot(x);
        access(y);
        splay(y);
    }

    // 查找在树的根
    // 因为lct要求满足中序遍历Splay得到的每个点的深度序列严格递增 根的深度最小
    // 一直往左儿子就是了
    int findRoot(register int x) {
        access(x);
        splay(x);
        while (lc) {
            pushDown(x);
            x = lc;
        }
        // 现在x是root
        splay(x);
        return x;
    }

    // 连边
    inline void link(register int x, register int y) {
        makeRoot(x);
        if (findRoot(y) != x)
            f[x] = y;
    }

    // 断边
    inline void cut(register int x, register int y) {
        makeRoot(x);
        if (findRoot(y) == x && f[y] == x && !c[y][0]) {
            f[y] = c[x][1] = 0;
            pushUp(x);
        }
    }
}
using namespace LCT;


int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int u, v, c, q;
    cin >> n >> q;
    for (int i = 1; i <= n; i++) {
        w[i] = mul[i] = 1;
    }
    for (int i = 1; i < n; i++) {
        cin >> u >> v;
        link(u, v);

    }
    string op;
    while (q--) {
        cin >> op;
        if (op == "+") { // 将 u 到 v 的路径上的点的权值都加上自然数 c
            cin >> u >> v >> c;
            split(u, v);
            Add(v, c);

        } else if (op == "-") { //将树中原有边(u1,v1)删除 加入新的边(u2,v2)
            cin >> u >> v;
            cut(u, v);

            cin >> u >> v;
            link(u, v);

        } else if (op == "*") {//将 u 到 v 的路径上的点的权值都乘上自然数 c
            cin >> u >> v >> c;
            split(u, v);
            Mul(v, c);

        } else { //询问 u 到 v 的路径上的点的权值和 将答案对 51061 取模
            cin >> u >> v;
            split(u, v);
            cout << sum[v] << endl;

        }
    }
    return 0;
}

P2542 [AHOI2005] 航线规划

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m;
namespace LCT { // lct板子
    int h[N];// 缩点后x所在的点
    int w[N];// w[x]表示x的点权

    int rev[N];//rev为区间翻转懒标记数组
    int f[N];// 父节点
    int st[N];//栈
    int c[N][2];// 每个节点的两个儿子

    // 判断节点x是否为一个splay的根 如果不是根 返回true
    inline bool nRoot(register int x) {
        // 如果节点x是根 则其与其父亲节点之间连的是轻边
        // 如果连的是轻边 他的父亲的儿子里没有它
        return c[f[x]][0] == x || c[f[x]][1] == x;
    }

#define lc c[x][0]
#define rc c[x][1]

    // splay区间翻转操作
    inline void reverse(register int x) {
        swap(lc, rc);
        rev[x] ^= 1;
    }


    // 判断并释放懒标记
    inline void pushDown(register int x) {
        if (rev[x]) {
            swap(lc, rc);
            rev[lc] ^= 1, rev[rc] ^= 1;
            rev[x] = 0;
        }
    }

    // 更新信息
    inline void pushUp(register int x) {
        w[x] = w[lc] + w[rc] + 1;
    }

    // 一次旋转
    inline void rotate(register int x) {
        register int y = f[x], z = f[y], k = (c[y][1] == x), w = c[x][!k];
        if (nRoot(y))
            c[z][c[z][1] == y] = x;
        c[x][!k] = y;
        c[y][k] = w;

        if (w)
            f[w] = y;
        f[y] = x;
        f[x] = z;
        pushUp(y);//原来的父亲现在是儿子了 更新新儿子的信息 而新父亲的信息不是在这里更新的
    }

    // 旋转一颗splay树 改为以x为根节点
    inline void splay(register int x) {
        register int y = x, z = 0;
        st[++z] = y;//暂存当前点到根的整条路径 pushdown时一定要从上往下放标记
        while (nRoot(y)) st[++z] = y = f[y];
        while (z) pushDown(st[z--]);
        //然后再从下往上旋转 splay的基本操作
        while (nRoot(x)) {
            y = f[x];
            z = f[y];
            if (nRoot(y))
                rotate((c[y][0] == x) ^ (c[z][0] == y) ? x : y);
            rotate(x);
        }
        pushUp(x);//更新最终新父亲x的信息
    }

    inline int geth(register int x) {
        return x == h[x] ? x : h[x] = geth(h[x]);
    }

    // 打通当前根节点到指定节点的树链
    // 使得一条中序遍历以根开始、以指定点结束的splay出现
    inline void access(register int x) {
        for (int y = 0; x; y = x, x = f[x] = geth(f[x])) {
            splay(x);
            rc = y;
            pushUp(x);
        }
    }

    //换根
    inline void makeRoot(register int x) {
        access(x);
        splay(x);
        //reverse(x);
        rev[x] ^= 1;
    }

    //提取路径 拉出x->y的路径成为一个splay 然后以y为splay的根
    inline void split(register int x, register int y) {
        makeRoot(x);
        access(y);
        splay(y);
    }

    // 查找在树的根
    // 因为lct要求满足中序遍历Splay得到的每个点的深度序列严格递增 根的深度最小
    // 一直往左儿子就是了
    int findRoot(register int x) {
        access(x);
        splay(x);
        while (lc) {
            pushDown(x);
            x = lc;
        }
        // 现在x是root
        splay(x);
        return x;
    }

    // 连边
    inline void link(register int x, register int y) {
        makeRoot(x);
        if (findRoot(y) != x) {
            f[x] = y;
        }
    }

    // 断边
    inline void cut(register int x, register int y) {
        makeRoot(x);
        if (findRoot(y) == x && f[y] == x && !c[y][0]) {
            f[y] = c[x][1] = 0;
            pushUp(x);
        }
    }

    inline void del(register int x, register int y) {
        if (x) {
            h[x] = y;
            del(lc, y);
            del(rc, y);
        }
    }

    //双连通分量存在时 连接x和y
    inline void merge(register int x, register int y) {
        if (x == y) return;
        makeRoot(x);
        if (findRoot(y) != x) {
            f[x] = y;// 等于 link(x,y)
            return;
        }
        // 双连通分量 暴力缩点
        del(rc, x);// 将以x为根的splay树缩成一个点 都用x来表示
        rc = 0;
        pushUp(x);//缩点 删点
    }

}
using namespace LCT;

struct Edge {
    int u, v;

    bool operator<(const Edge b) const {
        return u < b.u || (u == b.u && v < b.v);
    }
} e[N];

struct Operation {
    int u, v, op;
} a[N];

int vis[N], res[N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int u, v;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        w[i] = 1;// 点权
        h[i] = i;// 类似并查集里的f[]
    }
    for (int i = 1; i <= m; i++) {
        cin >> u >> v;
        if (u > v) swap(u, v);
        e[i] = {u, v};
    }
    sort(e + 1, e + 1 + m);

    int op, id = 0;
    while (cin >> op && op != -1) {
        cin >> u >> v;
        // op=1 询问 u->v的关键路线
        // op=0 断边
        if (!op) {
            if (u > v) swap(u, v);
            vis[lower_bound(e + 1, e + 1 + m, (Edge) {u, v}) - e] = 1;
        }
        a[++id] = {u, v, op};
    }

    // 把没有断边的点 全部连起来
    for (int i = 1; i <= m; i++) {
        if (!vis[i]) {
            merge(geth(e[i].u), geth(e[i].v));
        }
    }
    
    int cnt = 0;
    for (int i = id; i; i--) {
        u = geth(a[i].u);
        v = geth(a[i].v);
        if (a[i].op) {
            split(u, v);
            res[++cnt] = w[v] - 1;
        } else {
            merge(u, v);
        }
    }

    for (int i = cnt; i; i--) {
        cout << res[i] << endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值