LCT维护边双连通分量、维护边权、维护子树信息



前言

算是LCT入门把hhh
希望能在去上自动机前发出去 QAQ

  1. 巨佬的LCT应用篇
    https://www.cnblogs.com/flashhu/p/9498517.html

一点思考

  1. 为什么缩点后只需要改动access函数
    首先,缩点是缩到了Splay的根节点,而splay操作不改变实链虚链,只在原splay上转转转,故对别的splay不会有影响,因此需要保证每次调用splay都是直接缩完点的点。这在操作中靠每次传入缩完点后的点保证,也靠access函数中每次跳缩完点后的点保证,splay函数本身不做修改
    其次,所有函数中,只有access函数会跳到别人家的splay上去,并改变原splay结构,故需要改每次跳的真正的父节点
    最后,剩余函数要么基于splay,要么基于access,故不用改

染色 - HYSBZ 2243


链接
https://cn.vjudge.net/problem/HYSBZ-2243
思路
还是原来那道染色,不过换用LCT做
由LCT中Splay的性质(中序遍历深度严格递增)可知,可对一个节点,维护其所维护的区间的最左端的颜色lcol(部分深度最浅的点)与最右端的颜色rcol(部分深度最深的点),再由此更新其维护的区间的颜色段数,核心代码就是下面这段啦

sum[x] = sum[lson] + sum[rson] + 1, lcol[x] = rcol[x] = col[x];
if(lson){
    lcol[x] = lcol[lson];
    if(rcol[lson] == col[x])    sum[x]--;
}
if(rson){
    rcol[x] = rcol[rson];
    if(lcol[rson] == col[x])    sum[x]--;
}

那么查询的时候只要split一下查就可以了
而修改还是用懒惰标记,此处注意!!做翻转的时候要同时交换lcol和rcol

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define lson ch[x][0]
#define rson ch[x][1]
using namespace std;
typedef long long ll;
const int N = 1e5 + 15;
const int inf = 0x3f3f3f3f;

inline int read(){
    int x = 0;
    char ch;
    while((ch = getchar()) < '0' || ch > '9');
    while('0' <= ch && ch <= '9'){
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x;
}

inline void writeln(int x){
    printf("%d\n", x);
}

struct LinkCutTree{
    int fa[N], ch[N][2], sum[N], lzy[N], lzy_col[N];
    int stk[N];
    int col[N], lcol[N], rcol[N];

    inline bool nRoot(int x){
        return ch[fa[x]][0] == x || ch[fa[x]][1] == x;
    }

    void pushUp(int x){
        sum[x] = sum[lson] + sum[rson] + 1, lcol[x] = rcol[x] = col[x];
        if(lson){
            lcol[x] = lcol[lson];
            if(rcol[lson] == col[x])    sum[x]--;
        }
        if(rson){
            rcol[x] = rcol[rson];
            if(lcol[rson] == col[x])    sum[x]--;
        }
    }

    void pushR(int x){
        swap(lson, rson);
        swap(lcol[x], rcol[x]);
        lzy[x] ^= 1;
    }

    void updateColor(int x, int c){
        sum[x] = lzy_col[x] = 1;
        col[x] = lcol[x] = rcol[x] = c;
    }

    void pushDown(int x){
        if(lzy[x]){
            if(lson)    pushR(lson);
            if(rson)    pushR(rson);
            lzy[x] = 0;
        }
        if(lzy_col[x]){
            if(lson)    updateColor(lson, col[x]);
            if(rson)    updateColor(rson, col[x]);
            lzy_col[x] = 0;
        }
    }

    void rotate(int x){
        int y = fa[x], z = fa[y];
        int p = (ch[y][1] == x), w = ch[x][p^1];
        if(nRoot(y))    ch[z][ch[z][1] == y] = x;
        ch[x][p^1] = y, ch[y][p] = w;
        if(w)   fa[w] = y;
        fa[y] = x, fa[x] = z;
        pushUp(y);
    }

    void splay(int x){
        int pstk = 0, y = x;
        for(y = x; nRoot(y); y = fa[y]){
            stk[++pstk] = y;
        }
        stk[++pstk] = y;
        while(pstk)     pushDown(stk[pstk--]);

        while(nRoot(x)){
            int y = fa[x], z = fa[y];
            if(nRoot(y))     rotate((ch[y][0] == x) ^ (ch[z][0] == y) ? x : y);
            rotate(x);
        }
        pushUp(x);
    }

    void access(int x){
        for(int y = 0; x; y = x, x = fa[x]){
            splay(x);
            rson = y;
            pushUp(x);
        }
    }

    void makeRoot(int x){
        access(x);
        splay(x);
        pushR(x);
    }

    int findRoot(int x){
        access(x);
        splay(x);
        while(lson){
            pushDown(x);
            x = lson;
        }
        return x;
    }

    void split(int x, int y){
        makeRoot(x);
        access(y);
        splay(y);
    }

    void link(int x, int y){
        makeRoot(x);
        if(findRoot(y) != x)    fa[x] = y;
    }

    void cut(int x, int y){
        makeRoot(x);
        if(findRoot(y) == x && fa[x] == y && !rson){
            fa[x] = ch[y][0] = 0;
            pushUp(y);
        }
    }
};

LinkCutTree lct;

int main(){
    int n = read(),m = read();
    for(int i = 1; i <= n; i++){
        lct.lcol[i] = lct.rcol[i] = lct.col[i] = read();
        lct.sum[i] = 1;
    }
    for(int i = 1; i <= n - 1; i++){
        int u = read(), v = read();
        lct.link(u, v);
    }
    while(m--){
        char op[2];
        scanf("%s", op);
        if(op[0] == 'Q'){
            int u = read(), v = read();
            lct.split(u, v);
            writeln(lct.sum[v]);
        }else{
            int u = read(), v = read(), c = read();
            lct.split(u, v);
            lct.updateColor(v, c);
        }
    }
}


洛谷P2542 - [AHOI2005]航线规划


链接
https://www.luogu.org/problemnew/show/P2542
思路 —— LCT维护双连通分量
正向删边当然要改成 逆向添边,才好用并查集维护双连通分量
因此应该先把边读进来排个序,方便二分查找,然后再把操作读进来,对于操作中的删边操作,给读进来的边打个标记代表删除,再把剩余的边加入LCT中,并维护点的数量
在把剩余边加入LCT以及接下来逆向添边时,用并查集维护两点是否位于同一双连通中(缩点),那么在添边时,
若两点位于同一连通分量,则直接返回,否则会错!!!
若不在连通分量中,而又位于同一棵树上,那么两点split的链就都位于同一双连通分量,DFS暴力合并到同一并查集进行缩点(缩到Splay的根节点),再取消这棵splay上所有点的ch,掉除了缩点后的点以外所有点的fa
若不位于同一棵树上,那么就用普通的LCT连接即可
注意因为缩点了,因此需要修改access中的x = fa[x],令其为x = fa[x] = ft[fa[x]],这样才能真正跳到缩为一点的点上,添边操作与查询操作也需要找缩完的点
最后查询答案,两点split后桥的数量就是Splay中点数 - 1

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#define lson ch[x][0]
#define rson ch[x][1]
using namespace std;
typedef long long ll;
const int N = 100000 + 15;
const int inf = 0x3f3f3f3f;

inline int read(){
    int x = 0, tag = 1;
    char ch;
    while(((ch = getchar()) < '0' || ch > '9') && ch != '-');
    if(ch == '-'){
        tag = 0;
        ch = getchar();
    }
    while('0' <= ch && ch <= '9'){
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return tag ? x : -x;
}

int ft[N];

int find(int x){
    return ft[x] == x ? x : ft[x] = find(ft[x]);
}

void merge(int x, int y){
    int p = find(x), q = find(y);
    if(p != q)      ft[q] = p;
}

struct LinkCutTree{
    int fa[N], ch[N][2], sum[N], val[N], lzy[N];
    int stk[N];

    inline bool nRoot(int x){
        return ch[fa[x]][0] == x || ch[fa[x]][1] == x;
    }

    void pushUp(int x){
        sum[x] = sum[lson] + sum[rson] + 1;
    }

    void pushR(int x){
        swap(lson, rson);
        lzy[x] ^= 1;
    }

    void pushDown(int x){
        if(lzy[x]){
            if(lson)    pushR(lson);
            if(rson)    pushR(rson);
            lzy[x] = 0;
        }
    }

    void rotate(int x){
        int y = fa[x], z = fa[y];
        int p = (ch[y][1] == x), w = ch[x][p^1];
        if(nRoot(y))    ch[z][ch[z][1] == y] = x;
        ch[x][p^1] = y, ch[y][p] = w;
        if(w)   fa[w] = y;
        fa[y] = x, fa[x] = z;
        pushUp(y);
    }

    void splay(int x){
        int pstk = 0, y = x;
        for(y = x; nRoot(y); y = fa[y]){
            stk[++pstk] = y;
        }
        stk[++pstk] = y;
        while(pstk)     pushDown(stk[pstk--]);

        while(nRoot(x)){
            int y = fa[x], z = fa[y];
            if(nRoot(y))     rotate((ch[y][0] == x) ^ (ch[z][0] == y) ? x : y);
            rotate(x);
        }
        pushUp(x);
    }

    void access(int x){
        for(int y = 0; x; y = x, x = fa[x] = find(fa[x])){
            splay(x);
            rson = y;
            pushUp(x);
        }
    }

    void makeRoot(int x){
        access(x);
        splay(x);
        pushR(x);
    }

    int findRoot(int x){
        access(x);
        splay(x);
        while(lson){
            pushDown(x);
            x = lson;
        }
        return x;
    }

    void split(int x, int y){
        makeRoot(x);
        access(y);
        splay(y);
    }

    void dfs(int x, int u){
        merge(x, u);
        if(lson)    dfs(lson, u);
        if(rson)    dfs(rson, u);
        fa[x] = lson = rson = 0;
    }

    void link(int x, int y){
        if(x == y)  return;
        makeRoot(x);
        if(findRoot(y) != x){
            fa[x] = y;
        }else{
            dfs(ch[y][0], y);
            ch[y][0] = ch[y][1] = 0;
            pushUp(y);
        }
    }
};

struct edge{
    int u, v;
    bool operator < (const edge& b) const{
        if(u != b.u)    return u < b.u;
        else            return v < b.v;
    }
};
struct in{
    int op, u, v;
};

LinkCutTree lct;
vector<in> input;
vector<int> ans;
edge e[N];
bool used[N];

inline void init(){
    for(int i = 1; i < N; i++){
        ft[i] = i;
    }
}

int main(){
    init();
    int n = read(), m = read();
    for(int i = 0; i < m; i++){
        int u = read(), v = read();
        if(u > v)   swap(u, v);
        e[i] = edge{u, v};
    }
    sort(e, e + m);

    while(true){
        int op = read();
        if(op == -1)    break;

        int u = read(), v = read();
        if(u > v)   swap(u, v);
        input.emplace_back(in{op, u, v});
        if(op == 0){
            int pos = lower_bound(e, e + m, edge{u, v}) - e;
            used[pos] = true;
        }
    }

    for(int i = 0; i < m; i++){
        if(used[i])     continue;
        lct.link(find(e[i].u), find(e[i].v));
    }

    for(vector<in>::reverse_iterator it = input.rbegin(); it != input.rend(); it++){
        in& ele = *it;
        int u = find(ele.u), v = find(ele.v);
        if(ele.op == 1){
            lct.split(u, v);
            ans.emplace_back(lct.sum[v] - 1);
        }else{
            lct.link(u, v);
        }
    }

    for(vector<int>::reverse_iterator it = ans.rbegin(); it != ans.rend(); it++){
        printf("%d\n", *it);
    }
}


洛谷P4180 -【模板】严格次小生成树[BJWC2010]


链接
https://www.luogu.org/problemnew/show/P4180
思路 —— LCT与生成树
正解并非LCT,只是用LCT可做,但是开了氧气优化
首先一个基本结论是 最小生成树与次小生成树之间只有一条边之差
因此按Kruskal的基本思路,排个序再添边,若不位于同一连通分量则直接link,否则尝试替换环中最大边或次大边(split操作),并更新差值delta,找到最小delta,再加上最小生成树的权值和就是答案了
在这里为什么要替换最大边或次大边呢,因为排个序后再添边,若遇到已经位于同一连通分量的,说明该边权值肯定不小于里面的,那么替换最大边是最划算的,但是可能等于,题目要求严格次小,所以当最大值与改边相同时,那么需要替换次小边,因此需要用LCT维护区间最大值和次大值
另外,findRoot较慢,改用并查集,然而不加氧气还是过不了QAQ

// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define lson ch[x][0]
#define rson ch[x][1]
using namespace std;
typedef long long ll;
const int N = 3e5 + 15;
const int inf = 0x3f3f3f3f;

inline char get(void) {
    static char buf[1000000], *p1 = buf, *p2 = buf;
    if (p1 == p2) {
        p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin);
        if (p1 == p2) return EOF;
    }
    return *p1++;
}

inline void read(int& x) {
    x = 0; static char c; bool minus = false;
    for (; !(c >= '0' && c <= '9'); c = get()) if (c == '-') minus = true;
    for (; c >= '0' && c <= '9'; x = x * 10 + c - '0', c = get()); if (minus) x = -x;
}

char WritellBuffer[1024];
inline void write(ll a,char end){
    ll cnt=0,fu=1;
    if(a<0){putchar('-');fu=-1;}
    do{WritellBuffer[++cnt]=fu*(a%10)+'0';a/=10;}while(a);
    while(cnt){putchar(WritellBuffer[cnt]);--cnt;}
    putchar(end);
}

struct edge{
    int u, v, w;
    bool operator < (const edge& b) const{
        return w < b.w;
    }
};

edge e[N];
int  ft[N];

inline void init(){
    for(int i = 1; i < N; i++){
        ft[i] = i;
    }
}

int find(int x){
    return ft[x] == x ? x : ft[x] = find(ft[x]);
}

void merge(int x, int y){
    int p = find(x), q = find(y);
    if(p != q)  ft[q] = p;
}

struct LinkCutTree{
    int fa[N], ch[N][2], mx1[N], mx2[N], val[N], lzy[N];
    int stk[N];

    inline bool nRoot(int x){
        return ch[fa[x]][0] == x || ch[fa[x]][1] == x;
    }

    void pushUp(int x){
        if(mx1[lson] > mx1[rson]){
            mx1[x] = mx1[lson];
            mx2[x] = max(mx2[lson], mx1[rson]);
        }else if(mx1[lson] < mx1[rson]){
            mx1[x] = mx1[rson];
            mx2[x] = max(mx2[rson], mx1[lson]);
        }else{
            mx1[x] = mx1[lson];
            mx2[x] = max(mx2[lson], mx2[rson]);
        }

        if(val[x] > mx1[x]){
            mx2[x] = mx1[x];
            mx1[x] = val[x];
        }else if(val[x] != mx1[x] && val[x] > mx2[x]){
            mx2[x] = val[x];
        }
    }

    void pushR(int x){
        swap(lson, rson);
        lzy[x] ^= 1;
    }

    void pushDown(int x){
        if(lzy[x]){
            if(lson)    pushR(lson);
            if(rson)    pushR(rson);
            lzy[x] = 0;
        }
    }

    void rotate(int x){
        int y = fa[x], z = fa[y];
        int p = (ch[y][1] == x), w = ch[x][p^1];
        if(nRoot(y))    ch[z][ch[z][1] == y] = x;
        ch[x][p^1] = y, ch[y][p] = w;
        if(w)   fa[w] = y;
        fa[y] = x, fa[x] = z;
        pushUp(y);
    }

    void splay(int x){
        int pstk = 0, y = x;
        for(y = x; nRoot(y); y = fa[y]){
            stk[++pstk] = y;
        }
        stk[++pstk] = y;
        while(pstk)     pushDown(stk[pstk--]);

        while(nRoot(x)){
            int y = fa[x], z = fa[y];
            if(nRoot(y))     rotate((ch[y][0] == x) ^ (ch[z][0] == y) ? x : y);
            rotate(x);
        }
        pushUp(x);
    }

    void access(int x){
        for(int y = 0; x; y = x, x = fa[x]){
            splay(x);
            rson = y;
            pushUp(x);
        }
    }

    void makeRoot(int x){
        access(x);
        splay(x);
        pushR(x);
    }

    int findRoot(int x){
        access(x);
        splay(x);
        while(lson){
            pushDown(x);
            x = lson;
        }
        return x;
    }

    void split(int x, int y){
        makeRoot(x);
        access(y);
        splay(y);
    }
};
LinkCutTree lct;

int main(){
    int n, m;
    read(n);
    read(m);
    init();
    for(int i = 1; i <= m; i++){
        read(e[i].u);
        read(e[i].v);
        read(e[i].w);
    }
    sort(e + 1, e + m + 1);

    ll sum = 0;
    int delta = inf;
    int pp = n;
    for(int i = 1; i <= m; i++){
        int u = e[i].u, v = e[i].v, w = e[i].w;
        if(find(u) == find(v)){
            lct.split(u, v);
            delta = min(delta, w - (w > lct.mx1[v] ? lct.mx1[v] : lct.mx2[v]));
        }else{
            lct.makeRoot(u);
            lct.fa[u] = ++pp;
            lct.fa[pp] = v;
            lct.mx1[pp] = lct.val[pp] = w;
            sum += w;
            merge(pp, u);
            merge(v, pp);
            //printf("%d--\n", w);
        }
    }
    write(sum + delta, '\n');
}


洛谷P4219 - [BJOI2014]大融合


链接
https://www.luogu.org/problemnew/show/P4219
思路 —— LCT维护子树大小
无外乎把(u, v)两点之间的边cut掉然后求个联通块的点数乘积,再连回去
但是split后求虚儿子的点数 + 1的乘积更好
对于解法1,记得求之前需要makeroot,因为维护的是子树点数,那么需要其为splay的根才能求得联通块点数

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define lson ch[x][0]
#define rson ch[x][1]
using namespace std;
typedef long long ll;
const int N = 1e5 + 15;
const int inf = 0x3f3f3f3f;

/*
inline char get(void) {
    static char buf[1000000], *p1 = buf, *p2 = buf;
    if (p1 == p2) {
        p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin);
        if (p1 == p2) return EOF;
    }
    return *p1++;
}
*/

inline int read() {
    int x = 0; static char c; bool minus = false;
    for (; !(c >= '0' && c <= '9'); c = getchar()) if (c == '-') minus = true;
    for (; c >= '0' && c <= '9'; x = x * 10 + c - '0', c = getchar()); if (minus) x = -x;
    return x;
}

char WritellBuffer[1024];
template <typename T>
inline void write(T a,char end){
    ll cnt=0,fu=1;
    if(a<0){putchar('-');fu=-1;}
    do{WritellBuffer[++cnt]=fu*(a%10)+'0';a/=10;}while(a);
    while(cnt){putchar(WritellBuffer[cnt]);--cnt;}
    if(end) putchar(end);
}

struct LinkCutTree{
    int fa[N], ch[N][2], sum[N], val[N], lzy[N];
    int stk[N];
    int si[N];

    inline bool nRoot(int x){
        return ch[fa[x]][0] == x || ch[fa[x]][1] == x;
    }

    void pushUp(int x){
        sum[x] = sum[lson] + sum[rson] + si[x] + 1;
    }

    void pushR(int x){
        swap(lson, rson);
        lzy[x] ^= 1;
    }

    void pushDown(int x){
        if(lzy[x]){
            if(lson)    pushR(lson);
            if(rson)    pushR(rson);
            lzy[x] = 0;
        }
    }

    void rotate(int x){
        int y = fa[x], z = fa[y];
        int p = (ch[y][1] == x), w = ch[x][p^1];
        if(nRoot(y))    ch[z][ch[z][1] == y] = x;
        ch[x][p^1] = y, ch[y][p] = w;
        if(w)   fa[w] = y;
        fa[y] = x, fa[x] = z;
        pushUp(y);
    }

    void splay(int x){
        int pstk = 0, y = x;
        for(y = x; nRoot(y); y = fa[y]){
            stk[++pstk] = y;
        }
        stk[++pstk] = y;
        while(pstk)     pushDown(stk[pstk--]);

        while(nRoot(x)){
            int y = fa[x], z = fa[y];
            if(nRoot(y))     rotate((ch[y][0] == x) ^ (ch[z][0] == y) ? x : y);
            rotate(x);
        }
        pushUp(x);
    }

    void access(int x){
        for(int y = 0; x; y = x, x = fa[x]){
            splay(x);
            si[x] += sum[rson];
            si[x] -= sum[y];
            rson = y;
            pushUp(x);
        }
    }

    void makeRoot(int x){
        access(x);
        splay(x);
        pushR(x);
    }

    int findRoot(int x){
        access(x);
        splay(x);
        while(lson){
            pushDown(x);
            x = lson;
        }
        return x;
    }

    void split(int x, int y){
        makeRoot(x);
        access(y);
        splay(y);
    }

    void link(int x, int y){
        makeRoot(x);
        if(findRoot(y) != x){
            si[y] += sum[x];
            fa[x] = y;
        }
    }

    void cut(int x, int y){
        makeRoot(x);
        if(findRoot(y) == x && fa[x] == y && !rson){
            fa[x] = ch[y][0] = 0;
            pushUp(y);
        }
    }
};

LinkCutTree lct;

int main(){
    int n = read(), m = read();
    char op[2];
    while(m--){
        scanf("%s", op);
        int x = read(), y = read();
        if(op[0] == 'A'){
            lct.link(x, y);
        }else{
            lct.split(x, y);
            write((ll)(lct.si[x] + 1) * (lct.si[y] + 1), '\n');
        }
    }
}