题意
操作1:将两点所在的区间合并。
操作2:将一点移至另一点所在集合内。
操作3:求一点所在区间的元素个数和元素的总和。
分析
并查集的大小以及元素和是很好计算的,在合并时将“认亲”的点的对应值加给它祖先就行了。主要是操作2。
对于一个点单独抽离出来,我们发现是不好弄的,按照常规的方法做的话,如果直接以它向集合的祖先相连,则它的子孙们也会受影响。而去除这一影响的关键就在于建虚点。即用一个实际并不存在的点来代表集合的祖先,这样挪走集合内的任意一个点,对其它点都不会造成影响,因为没有人会管它。于是我们在初始给每个点创造一个属于它自己的虚点,照常合并即可。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,fa[200005],m,x,y,op;
int sum[200005],siz[200005];
int p,q;
inline void read(int &res){
char c;
int f=1;
res=0;
c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')res=(res<<1)+(res<<3)+c-48,c=getchar();
res*=f;
}
inline int find(int x){
if(fa[x]!=x)return fa[x]=find(fa[x]);//压缩路径
return x;
}
signed main(){
while(cin>>n>>m){
for(int i=1;i<=n;i++)fa[i]=i+n,sum[i+n]=i;
for(int i=n+1;i<=2*n;i++)fa[i]=i,siz[i]=1;//建虚点
while(m--){
cin>>op;
if(op==1){
read(p);read(q);
int x=find(p),y=find(q);
if(x==y)continue;
if(siz[x]<siz[y]){//按秩合并
fa[x]=y;
sum[y]+=sum[x];//合并
siz[y]+=siz[x];
}
else {
fa[y]=x;
sum[x]+=sum[y];
siz[x]+=siz[y];
}
}
if(op==2){
read(p);read(q);
int x=find(p),y=find(q);
if(x==y)continue;
fa[p]=y;
sum[y]+=p;
sum[x]-=p;
siz[y]++;
siz[x]--;
}
if(op==3){
read(q);
int x=find(q);
cout<<siz[x]<<" "<<sum[x]<<endl;
}
}
}
return 0;
}