LCT 的运用

维护连通性

对一个森林,进行连边,删边操作(保证任何时候整个图是一个森林)和查询两个点是否连通。

做法

连边/删边是基础操作。

LCT 可以查找某个点 \(u\) 所在的树的树根,具体方法是,对 \(u\) access 再 splay,此时 \(u\) 和根在同一个 Splay 内,那么这个 Splay 中序遍历下的第一个节点就是树根。

具体方法是从这个 Splay 的根出发不断跳左儿子,直到没有左儿子,将这个点返回即可。(注意在返回前要把这个点 splay 上去以保证复杂度)

对于两个点 \(u, v\),如果它们找到的原树的根相同,则它们连通。

需要注意的是题目必须保证任何时候整个图是一个森林,否则不能这么做。

例题

洞穴勘测

点击查看代码
 #include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, a, b) for(ll i=a;i<=b;i++)
#define rrep(i, a, b) for(ll i=a;i>=b;i--)
const ll N=10009;
ll n, m;

struct LCT{
    #define ls(u) tr[u].l
    #define rs(u) tr[u].r
    #define fa(u) tr[u].fa
    #define isleft(u) (ls(tr[u].fa)==u?1:0)
    #define isroot(u) (ls(fa(u))!=u&&rs(fa(u))!=u?1:0)
    #define son(u, ty) (ty==1?ls(u):rs(u))
    struct point{
        ll l, r, fa, tag;
    }tr[N];
    void rotate(ll u){
        ll y=fa(u), z=fa(y);
        if(isleft(u)){
            ll p2=rs(u), ty=isleft(y);
            fa(u)=z;rs(u)=y;
            fa(y)=u;ls(y)=p2;
            if(p2)fa(p2)=y;
            if(son(z, ty)==y)son(z, ty)=u;
        }else{
            ll p2=ls(u), ty=isleft(y);
            fa(u)=z;ls(u)=y;
            fa(y)=u;rs(y)=p2;
            if(p2)fa(p2)=y;
            if(son(z, ty)==y)son(z, ty)=u;
        }
    }
    void push_down(ll u){
        if(tr[u].tag){
            if(ls(u)){swap(ls(ls(u)), rs(ls(u)));tr[ls(u)].tag^=1;}
            if(rs(u)){swap(ls(rs(u)), rs(rs(u)));tr[rs(u)].tag^=1;}
            tr[u].tag=0;
        }
    }
    void update(ll u){
        if(fa(u))update(fa(u));
        push_down(u);
    }
    void splay(ll u){
        update(u);
        while(!isroot(u)){
            if(isroot(fa(u))){//zig
                rotate(u);return;
            }
            if(isleft(u)==isleft(fa(u))){
                rotate(fa(u));rotate(u);
            }else{
                rotate(u);rotate(u);
            }
        }
    }
    
    ll access(ll u){
        ll p=0;
        while(u){
            splay(u);rs(u)=p;
            p=u;u=fa(u);
            
        }
        return p;
    }
    void makeroot(ll u){
        ll y=access(u);
        swap(ls(y), rs(y));
        tr[y].tag^=1;
    }
    void link(ll u, ll v){//保证u和不连通
        makeroot(u);
        splay(u);fa(u)=v;
    }
    void cut(ll u, ll v){//保证存在u-v
        makeroot(u);access(v);splay(v);
        ls(v)=0;fa(u)=0;
    }
    ll find(ll u){//找到u所在树的原树根
        ll p=access(u);
        push_down(p);
        while(ls(p)){p=ls(p);push_down(p);}
        splay(p);
        return p;
    }
}lct;
int main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin >> n >> m;
    rep(i, 1, m){
        string s;cin >> s;
        ll u, v;cin >>u >> v;
        if(s=="Connect"){
            lct.link(u, v);
        }else if(s=="Destroy"){
            lct.cut(u, v);
        }else{
            if(lct.find(u)==lct.find(v)){
                cout << "Yes" << endl;
            }else{
                cout << "No" << endl;
            }
        }
    }
    return 0;
}

查询边(点)权和/边(点)权修改

对一个森林,进行连边,删边操作(保证任何时候整个图是一个森林),修改某个边(点)的权值和查询两个点之间路径的边(点)权和。

解法

先考虑点权怎么维护。

  • 点权修改:将这个点 splay 到树根,再修改点权即可。

  • 路径点权和查询:将一个点 \(u\) 设为原树的根,然后 access(v),此时 \(u\)\(v\) 之间的路径恰好在同一 Splay 中,直接输出这个 Splay 的点权和。

维护边权的方法是对于每一条边 \((u, v)\),建立一个虚点 \(z\),虚点的点权是原来的边权,让 \(z\) 连接 \(u\)\(v\)。这样问题就变为了点权问题。

例题

【模板】动态树(LCT)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, a, b) for(ll i=a;i<=b;i++)
#define rrep(i, a, b) for(ll i=a;i>=b;i--)
const ll N=1e5+9;
ll n, m;

struct LCT{//动态树
    #define ls(u) tr[u].l
    #define rs(u) tr[u].r
    #define fa(u) tr[u].fa
    #define pos(u) (ls(fa(u))==u?0:(rs(fa(u))==u?1:-1))
    //0为左儿子,1为右儿子,-1表示u是单元splay的根
    #define son(u, ty) (ty==0?ls(u):rs(u))
    #define isroot(u) (ls(fa(u))!=u&&rs(fa(u))!=u)//判断u是否是单元Splay的根
    #define pushup(u) tr[u].xorV=tr[u].val^(ls(u)?tr[ls(u)].xorV:0)^(rs(u)?tr[rs(u)].xorV:0);
    void pushdown(ll u){ 
        if(tr[u].p){
            if(ls(u)){tr[ls(u)].p^=1;swap(ls(ls(u)), rs(ls(u)));}
            if(rs(u)){tr[rs(u)].p^=1;swap(ls(rs(u)), rs(rs(u)));}
            tr[u].p=0;
        }
    }
    struct point{
        ll l, r, val, p, xorV, fa;//xorV为它领导的单元Splay上的子树异或和
    }tr[N];
    void rotate(ll u){
        ll y=fa(u), z=fa(y);
        pushdown(y);pushdown(u);
        if(pos(u)==0){
            ll p2=rs(u), ty=pos(y);
            fa(u)=z;rs(u)=y;
            fa(y)=u;ls(y)=p2;
            if(p2)fa(p2)=y;
            if(ty!=-1)son(z, ty)=u;
        }else{
            ll p2=ls(u), ty=pos(y);
            fa(u)=z;ls(u)=y;
            fa(y)=u;rs(y)=p2;
            if(p2)fa(p2)=y;
            if(ty!=-1)son(z, ty)=u;
        }
        pushup(y);
        pushup(u);
    }
    void update(ll u){
        if(!isroot(u))update(fa(u));
        pushdown(u);
    }
    void splay(ll u){//将u旋转到u所在的单元Splay的根
        update(u);//!必须update的原因,如果不updata,原本要zig-zag的变成了zig-zig,原本要zig-zig的变成了zig-zag,复杂度错误
        while(!isroot(u)){
            if(isroot(fa(u))){
                rotate(u);return;
            }
            if(pos(u)==pos(fa(u))){
                rotate(fa(u));rotate(u);
            }else{
                rotate(u);rotate(u);
            }
        }
    }
    ll access(ll u){
        ll p=0;
        while(u){
            splay(u);pushdown(u);rs(u)=p;pushup(u);//!忘记pushup
            p=u;u=fa(u);
        }
        return p;
    }
    ll find(ll u){//找到u所在的联通块的原根
        ll p=access(u);   
        pushdown(p);
        while(ls(p)){p=ls(p);pushdown(p);}
        return p;
    }
    void makeroot(ll u){
       ll p=access(u);
       swap(ls(p), rs(p));
       tr[p].p^=1;
    }
    bool link(ll u, ll v){
        if(find(u)==find(v))return 0;
        makeroot(u);
        splay(u);
        fa(u)=v;
        return 1;
    }
    void cut(ll u, ll v){
        makeroot(u);
        access(v);
        splay(v);
        pushdown(v);
        ls(v)=fa(u)=0;
        pushup(v);//每次树形态的修改都要pushup和pushdown
    }
    void change(ll u, ll x){
        splay(u);
        tr[u].val=x;
        pushup(u);
    }
    void printtree(){
        rep(u, 1, n){
            cout << ls(u) << ' ' << rs(u) << ' ' << tr[u].val << ' ' << fa(u) << ' ' << tr[u].xorV << ' ' << tr[u].p << endl;
        }
        cout << endl;
    }
    ll query(ll u, ll v){
        makeroot(u);
        ll p=access(v);
        return tr[p].xorV;
    }
    
}lct;
set <pair<ll, ll> > st;

int main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin >> n >> m;
    rep(i, 1, n){
        ll a;cin >> a;
        lct.change(i, a);
    }
    rep(i, 1, m){
        ll ty, x, y;cin >> ty >> x >> y;
        if(ty==0){
            cout << lct.query(x, y) << endl;
        }else if(ty==1){
            if(x>y)swap(x, y);
            if(lct.link(x, y))st.insert(make_pair(x, y));
        }else if(ty==2){
            if(x>y)swap(x, y);
            if(st.count(make_pair(x, y))){
                st.erase(make_pair(x, y));
                lct.cut(x, y);
            }
        }else{
            lct.change(x, y);
        }
    }
    return 0;
}

动态维护最小生成树

对一个无向图,进行加边操作(不保证图是森林)以及查询整张图是否连通,如果连通,则输出整张图的最小生成树边权和。

解法

在加入边 \(w(u, v)\) 时,如果 \(u\), \(v\) 不连通,直接 link,否则,找到路径上边权最大的边,设其为 \(w'\)

如果 \(w'\) 的边权小于 \(w\) 的边权,不做任何操作,否则,删去 \(w'\),连接 \(w\)

例题

【模板】最小生成树

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, a, b) for(ll i=a;i<=b;i++)
const ll N= 400009;
#define ls(u) tr[u].l
#define rs(u) tr[u].r
#define fa(u) tr[u].fa
#define pos(u) (ls(fa(u))==u?0:rs(fa(u))==u?1:-1)
#define son(u, ty) (ty==0?ls(u):rs(u))
struct LCT{//支持断边/连边/修改点权值/查询路径点权和
    struct point{
        ll l, r, fa, p, val, mx, mxk;
    }tr[N];
    #define mx(u) (tr[u].mx)
    #define val(u) (tr[u].val)
    #define isroot(u) (pos(u)==-1?1:0)
    void pushdown(ll u){//!什么时候要pushup/down?1.rotate里面,2.如果一个点的儿子要修改/访问,修改前pushdown,修改后pushup 3.修改这个点的点权要pushup
        if(tr[u].p){
            tr[ls(u)].p^=1;
            tr[rs(u)].p^=1;
            swap(ls(ls(u)), rs(ls(u)));
            swap(ls(rs(u)), rs(rs(u)));
            tr[u].p=0;
        }
    }
    void pushup(ll u){
        mx(u)=max(val(u), max(mx(ls(u)), mx(rs(u))));
        if(val(u)==mx(u))tr[u].mxk=u;
        else if(ls(u)&&mx(ls(u))==mx(u))tr[u].mxk=tr[ls(u)].mxk;
        else if(rs(u)&&mx(rs(u))==mx(u))tr[u].mxk=tr[rs(u)].mxk;
    }
    void rotate(ll u){
        ll y=fa(u), z=fa(y);
        pushdown(y);pushdown(u);
        if(pos(u)==0){
            ll p2=rs(u), t=pos(y);
            rs(u)=y;
            fa(u)=z;
            fa(y)=u;
            ls(y)=p2;
            if(p2)fa(p2)=y;
            if(t!=-1)son(z, t)=u;
        }else{
            ll p2=ls(u), t=pos(y);
            ls(u)=y;
            fa(u)=z;
            fa(y)=u;
            rs(y)=p2;
            if(p2)fa(p2)=y;
            if(t!=-1)son(z, t)=u;
        }
        pushup(y);pushup(u);
    }
    void pushdownAll(ll u){
        if(isroot(u)){pushdown(u);return;}
        pushdownAll(fa(u));pushdown(u);
    }
    void splay(ll u){
        pushdownAll(u);//!这句话漏掉了
        while(!isroot(u)){
            if(isroot(fa(u))){
                rotate(u);return;
            }
            if(pos(u)==pos(fa(u))){
                rotate(fa(u));
                rotate(u);
            }else{
                rotate(u);rotate(u);
            }
        }
    }
    ll access(ll u){
        ll k=0;
        while(u){
            splay(u);
            pushdown(u);
            rs(u)=k;
            pushup(u);
            k=u;
            u=fa(u);
        }
        return k;
    }
    void makeroot(ll u){
        ll p=access(u);
        tr[p].p^=1;
        swap(ls(p), rs(p));
    }
    void cut(ll u, ll v){//要求必须存在(u, v)这条边
        makeroot(u);
        access(v);
        splay(v);
        pushdown(v);
        ls(v)=fa(u)=0;
        pushup(v);
    }
    void link(ll u, ll v){//要求u和v必须不连通
        makeroot(u);
        splay(u);
        fa(u)=v;
    }
    void update(ll u, ll val){//修改u的点权
        splay(u);
        val(u)=val;
        pushup(u);
    }
    pair<ll, ll> findmx(ll u, ll v){//要求u和v必须连通
        makeroot(u);
        access(v);
        splay(v);
        return make_pair(mx(v), tr[v].mxk);
    }
    ll findroot(ll u){
        access(u);
        splay(u);
        pushdown(u);
        while(ls(u)){u=ls(u);pushdown(u);}
        splay(u);
        return u;
    }
}lct;
ll u[N], v[N];
int main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    ll n, m;
    cin >> n >> m;
    ll ans=0;
    ll cnt=0;
    rep(i, 1, m){
        ll x, y, z;cin >> x >> y >> z;
        u[i+n]=x;v[i+n]=y;
        if(lct.findroot(x)!=lct.findroot(y)){
            cnt++;
            ans+=z;
            lct.update(i+n, z);
            lct.link(x, i+n);
            lct.link(y, i+n);
        }else{
            auto mx=lct.findmx(x, y);
            if(mx.first<z)continue;
            ans+=z-mx.first;
            lct.cut(mx.second, u[mx.second]);
            lct.cut(mx.second, v[mx.second]);
            lct.update(i+n, z);
            lct.link(x, i+n);
            lct.link(y, i+n);
        }
    }
    if(cnt!=n-1){
        cout << "orz" << endl;
    }else{
        cout << ans << endl;
    }
    return 0;
}

最小差值生成树

考虑到最大边最小生成树就是最小生成树,因此按照边权从大到小的顺序加入边,设加入边权为 \(w_i\) 的边后,最小生成树的最大边权为 \(w'_i\),那么答案是 \(\min (w'_i-w_i)\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, a, b) for(ll i=a;i<=b;i++)
#define rrep(i, a, b) for(ll i=a;i>=b;i--)
const ll N= 400009, INF=1e16;
#define ls(u) tr[u].l
#define rs(u) tr[u].r
#define fa(u) tr[u].fa
#define pos(u) (ls(fa(u))==u?0:rs(fa(u))==u?1:-1)
#define son(u, ty) (ty==0?ls(u):rs(u))
struct LCT{//支持断边/连边/修改点权值/查询路径点权和
    struct point{
        ll l, r, fa, p, val, mx, mxk;
    }tr[N];
    #define mx(u) (tr[u].mx)
    #define val(u) (tr[u].val)
    #define isroot(u) (pos(u)==-1?1:0)
    void pushdown(ll u){//!什么时候要pushup/down?1.rotate里面,2.如果一个点的儿子要修改/访问,修改前pushdown,修改后pushup 3.修改这个点的点权要pushup
        if(tr[u].p){
            tr[ls(u)].p^=1;
            tr[rs(u)].p^=1;
            swap(ls(ls(u)), rs(ls(u)));
            swap(ls(rs(u)), rs(rs(u)));
            tr[u].p=0;
        }
    }
    void pushup(ll u){
        mx(u)=max(val(u), max(mx(ls(u)), mx(rs(u))));
        if(val(u)==mx(u))tr[u].mxk=u;
        else if(ls(u)&&mx(ls(u))==mx(u))tr[u].mxk=tr[ls(u)].mxk;
        else if(rs(u)&&mx(rs(u))==mx(u))tr[u].mxk=tr[rs(u)].mxk;
    }
    void rotate(ll u){
        ll y=fa(u), z=fa(y);
        pushdown(y);pushdown(u);
        if(pos(u)==0){
            ll p2=rs(u), t=pos(y);
            rs(u)=y;
            fa(u)=z;
            fa(y)=u;
            ls(y)=p2;
            if(p2)fa(p2)=y;
            if(t!=-1)son(z, t)=u;
        }else{
            ll p2=ls(u), t=pos(y);
            ls(u)=y;
            fa(u)=z;
            fa(y)=u;
            rs(y)=p2;
            if(p2)fa(p2)=y;
            if(t!=-1)son(z, t)=u;
        }
        pushup(y);pushup(u);
    }
    void pushdownAll(ll u){
        if(isroot(u)){pushdown(u);return;}
        pushdownAll(fa(u));pushdown(u);
    }
    void splay(ll u){
        pushdownAll(u);//!这句话漏掉了
        while(!isroot(u)){
            if(isroot(fa(u))){
                rotate(u);return;
            }
            if(pos(u)==pos(fa(u))){
                rotate(fa(u));
                rotate(u);
            }else{
                rotate(u);rotate(u);
            }
        }
    }
    ll access(ll u){
        ll k=0;
        while(u){
            splay(u);
            pushdown(u);
            rs(u)=k;
            pushup(u);
            k=u;
            u=fa(u);
        }
        return k;
    }
    void makeroot(ll u){
        ll p=access(u);
        tr[p].p^=1;
        swap(ls(p), rs(p));
    }
    void cut(ll u, ll v){//要求必须存在(u, v)这条边
        makeroot(u);
        access(v);
        splay(v);
        pushdown(v);
        ls(v)=fa(u)=0;
        pushup(v);
    }
    void link(ll u, ll v){//要求u和v必须不连通
        makeroot(u);
        splay(u);
        fa(u)=v;
    }
    void update(ll u, ll val){//修改u的点权
        splay(u);
        val(u)=val;
        pushup(u);
    }
    pair<ll, ll> findmx(ll u, ll v){//要求u和v必须连通
        makeroot(u);
        access(v);
        splay(v);
        return make_pair(mx(v), tr[v].mxk);
    }
    ll findroot(ll u){
        access(u);
        splay(u);
        pushdown(u);
        while(ls(u)){u=ls(u);pushdown(u);}
        splay(u);
        return u;
    }
}lct;
struct edge{
    ll u, v, w;
    bool operator < (const edge& rhs) const{
        return w<rhs.w;
    }
}e[N];
multiset <ll> ans;
ll u[N], v[N];
int main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    ll n, m;
    cin >> n >> m;
    rep(i, 1, m){
        cin >> e[i].u >> e[i].v >> e[i].w;
        if(e[i].u==e[i].v){e[i].w=INF;}
    }
    sort(e+1, e+m+1);
    while(e[m].u==e[m].v)m--;

    ll cnt=0;   
    ll res=INF;
    rrep(i, m, 1){
        ll x=e[i].u, y=e[i].v, z=e[i].w;
        u[i+n]=x;v[i+n]=y;
        if(lct.findroot(x)!=lct.findroot(y)){
            cnt++;
            ans.insert(z);
            lct.update(i+n, z);
            lct.link(x, i+n);
            lct.link(y, i+n);
        }else{
            auto mx=lct.findmx(x, y);
            if(mx.first<z)continue;
            ans.insert(z);
            ans.erase(ans.find(mx.first));
            lct.cut(mx.second, u[mx.second]);
            lct.cut(mx.second, v[mx.second]);
            lct.update(i+n, z);
            lct.link(x, i+n);
            lct.link(y, i+n);
        }
        if(cnt==n-1){
            res=min(res, *(ans.rbegin())-*(ans.begin()));
        }
    }
    cout << res << endl;
    return 0;
}

动态维护点双

有一个无向图。要求支持加边(不保证图是森林)以及查询某两个点是否在同一个边双内/某个点所在的边双中包含的点的数量。

解法

动态维护缩点的过程,不断将处于同一个边双内的点合并在一起。使用并查集维护这一过程。一个点在并差集上的祖先代表这个点出现在 LCT中。(所以 LCT 上的节点数量就是边双的数量)

具体来说,在每次加边 \((u, v)\) 时,考虑:

  • \(u\)\(v\) 不连通,则 link(u, v)

  • \(u\)\(v\) 已经处于同一边双中(并查集中已经连通),则不做任何操作。

  • \(u\)\(v\) 连通但不属于同一边双中,则先 makeroot(u)access(v),此时 \(u\)\(v\) 路径上的所有点都处于同一 Splay 中。

然后让这一颗 Splay 上的所有节点都用并查集连接到这个 Splay 的根,这样就起到了合并整颗 Splay 的效果。

Q:这么做仅仅让 Splay 上的节点在并查集上连在了一起,在 LCT 上还是分开的节点,怎么办?

A:将 Splay 的根 \(r\) 的左右儿子都设为空。

Q:但是其他 Splay 的根的父亲依然可能指向错误的节点而非 \(r\)

A:只需要将所有查询 \(u\) 的父亲的代码从 tr[u].fa 改为 find(tr[u].fa) 即可,这样就能找到了 \(r\)。(find(x) 表示并查集中 \(x\) 的根)

例题

BZOJ4998 星球联盟

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, a, b) for(ll i=a;i<=b;i++)
#define rrep(i, a, b) for(ll i=a;i>=b;i--)
const ll N= 400009, INF=1e16;
#define ls(u) tr[u].l
#define rs(u) tr[u].r
#define fa(u) (tr[u].fa)
#define pos(u) (ls(findl(fa(u)))==u?0:rs(findl(fa(u)))==u?1:-1)
#define son(u, ty) (ty==0?ls(u):rs(u))
ll bcj[N];
ll sz[N];
ll findl(ll u){
    if(bcj[u]==u)return u;
    return bcj[u]=findl(bcj[u]);
}
struct LCT{
    struct point{
        ll l, r, fa, p;
    }tr[N];
    #define mx(u) (tr[u].mx)
    #define val(u) (tr[u].val)
    #define isroot(u) (pos(u)==-1?1:0)
    void pushdown(ll u){//!什么时候要pushup/down?1.rotate里面,2.如果一个点的儿子要修改/访问,修改前pushdown,修改后pushup 3.修改这个点的点权要pushup
        if(tr[u].p){
            tr[ls(u)].p^=1;
            tr[rs(u)].p^=1;
            swap(ls(ls(u)), rs(ls(u)));
            swap(ls(rs(u)), rs(rs(u)));
            tr[u].p=0;
        }
    }
    void rotate(ll u){
        ll y=findl(fa(u)), z=findl(fa(y));
        pushdown(y);pushdown(u);
        if(pos(u)==0){
            ll p2=rs(u), t=pos(y);
            rs(u)=y;
            fa(u)=z;
            fa(y)=u;
            ls(y)=p2;
            if(p2)fa(p2)=y;
            if(t!=-1)son(z, t)=u;
        }else{
            ll p2=ls(u), t=pos(y);
            ls(u)=y;
            fa(u)=z;
            fa(y)=u;
            rs(y)=p2;
            if(p2)fa(p2)=y;
            if(t!=-1)son(z, t)=u;
        }
    }
    void pushdownAll(ll u){
        if(isroot(u)){pushdown(u);return;}
        pushdownAll(findl(fa(u)));pushdown(u);
    }
    void splay(ll u){
        pushdownAll(u);
        while(!isroot(u)){
            if(isroot(findl(fa(u)))){
                rotate(u);return;
            }
            if(pos(u)==pos(findl(fa(u)))){
                rotate(findl(fa(u)));
                rotate(u);
            }else{
                rotate(u);rotate(u);
            }
        }
    }
    ll access(ll u){
        ll k=0;
        while(u){
            splay(u);
            pushdown(u);
            rs(u)=k;
            k=u;
            u=findl(fa(u));
        }
        return k;
    }
    void makeroot(ll u){
        ll p=access(u);
        tr[p].p^=1;
        swap(ls(p), rs(p));
    }
    void cut(ll u, ll v){//要求必须存在(u, v)这条边
        makeroot(u);
        access(v);
        splay(v);
        pushdown(v);
        ls(v)=fa(u)=0;
    }
    void link(ll u, ll v){//要求u和v必须不连通
        makeroot(u);
        splay(u);
        fa(u)=v;
    }
    ll findroot(ll u){
        access(u);
        splay(u);
        pushdown(u);
        while(ls(u)){u=ls(u);pushdown(u);}
        splay(u);
        return u;
    }
    void mergeSplay(ll u, ll b){
        if(u!=b){
            sz[b]+=sz[u];
            bcj[u]=b;
        }
        if(ls(u))mergeSplay(ls(u), b);
        if(rs(u))mergeSplay(rs(u), b);
    }
    void merge(ll u, ll v){
        makeroot(u);
        ll p=access(v);//p是单位splay的根
        mergeSplay(p, p);
        ls(p)=rs(p)=0;
    }
}lct;
ll n, m, q;

int main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin >> n >> m >> q;
    rep(i, 1, n){
        bcj[i]=i;sz[i]=1;
    }
    rep(i, 1, m+q){
        ll u, v;cin >> u >> v;
        u=findl(u);v=findl(v);
        if(lct.findroot(u)!=lct.findroot(v)){
            lct.link(u, v);
            if(i>m){
                cout << "No\n";
            }
        }else{
            if(u==v){
                if(i>m){
                    cout << sz[u] << endl;
                }
                continue;
            }
            lct.merge(u, v);
            if(i>m){
                cout << sz[findl(u)] << endl;
            }
        }
    }
    return 0;
}
posted @ 2025-04-12 10:26  yanzihe  阅读(56)  评论(0)    收藏  举报