感觉这个题还是蛮灵活的,并没有死考一个并查集。
考虑如果没有2操作,那么这就是一个并查集的模板题。多出一个2操作增加了什么困难呢?
如果我们直接用并查集维护,那么2操作必须改变f[p],这样的话,原来那些在p子树里的元素也就跟着一起被移到q所在集合里了。
这显然是不对的。。。
其实我们要做的仅仅是把p这个点扣出来,而尽量使p原来所在的集合不太变动。
这么一想,就有了一个思路:每次进行2操作我们就新开一个点代表 新p,我们只需要把 f[新p] 设置成q的根,然后把原根的信息改一改,q的根的信息改一改就好了。对于1~n我们开一个数组记录它们的新点是多少,不管什么操作我们都用新点。
新算法的正确性在于:每次2操作开一个新点之后,原点就变成了一个仅有指示集合作用的虚点,并且在后续操作中(除了原来指向它的点)不会被用到;并且本题求的值都可以直接记录在并查集的根上,所以操作2后改一改两个根的信息以后就一劳永逸了,不会再用到没改的小树的错误信息。。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=100005;
int n,m,f[N*2],dy[N],num[N*2];
ll sum[N*2];
inline int read(){
int x=0; char ch=getchar();
for(;!isdigit(ch);ch=getchar());
for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
return x;
}
int getf(int x){ return f[x]==x?x:(f[x]=getf(f[x]));}
inline void solve(){
for(int i=1;i<=n;i++) f[i]=dy[i]=i,num[i]=1,sum[i]=i;
for(int opt,p,q;m;m--){
opt=read();
if(opt==3) p=getf(dy[read()]),printf("%d %lld
",num[p],sum[p]);
else if(opt==1){
p=getf(dy[read()]),q=getf(dy[read()]);
if(p!=q) f[p]=q,num[q]+=num[p],sum[q]+=sum[p];
}
else{
p=read(),q=getf(dy[read()]);
int fa=getf(dy[p]);
if(fa==q) continue;
num[fa]--,sum[fa]-=(ll)p;
dy[p]=++n,f[n]=q,num[q]++,sum[q]+=(ll)p;
}
}
}
int main(){
while(scanf("%d%d",&n,&m)==2) solve();
return 0;
}