有n棵树,要求支持4种操作:把两棵树所在森林合并;把一颗树从森林中分离出来;问某棵树所属森林的大小;问两棵树是否在同一个森林中。

如果没有查询树的大小(操作3)那肯定一个并查集解决。

加上这个操作3就麻烦了。

我们怎么维护一个森林的大小?

暴力必不可能。。。

每次合并和删除的时候直接改根节点也会出问题,因为不是所有节点都路径压缩过。

所以我们可以在把一个节点独立的时候直接舍去这个节点,新建一个新的节点来代替原来的节点。

然后根节点存的森林大小直接-1即可,因为之后的事和废弃节点无瓜。

这样就可以了。

附代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define MAXN 200010
using namespace std;
int n,m,fa[MAXN],size[MAXN],id[MAXN];
char tp[100000],*p1=tp,*p2=tp;
inline char get_char(){
    return p1==p2&&(p2=(p1=tp)+fread(tp,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int read(){
    int date=0,w=1;char c=0;
    while(c<'0'||c>'9'){if(c=='-')w=-1;c=get_char();}
    while(c>='0'&&c<='9'){date=date*10+c-'0';c=get_char();}
    return date*w;
}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void uniun(int x,int y){
    x=find(id[x]);y=find(id[y]);
    if(x!=y){
        fa[x]=y;
        size[y]+=size[x];
    }
}
bool check(int x,int y){x=find(id[x]);y=find(id[y]);return x==y;}
void remove(int x){size[find(id[x])]--;id[x]=++n;}
void work(){
    int f,x,y;
    while(m--){
        f=read();x=read();
        if(f==1){
            y=read();
            uniun(x,y);
        }
        else if(f==2){
            remove(x);
        }
        else if(f==3){
            printf("%d\n",size[find(id[x])]);
        }
        else if(f==4){
            y=read();
            printf(check(x,y)?"YES\n":"NO\n");
        }
    }
}
void init(){
    memset(fa,0,sizeof(fa));
    memset(size,0,sizeof(size));
    memset(id,0,sizeof(id));
    n=read();m=read();
    for(int i=1;i<=n+m;i++){
        id[i]=fa[i]=i;
        size[i]=1;
    }
}
int main(){
    int t=read();
    for(int cases=1;cases<=t;cases++){
        printf("Case #%d:\n",cases);
        init();
        work();
    }
    return 0;
}