题目链接:https://cn.vjudge.net/contest/320014#problem/D
前提:并查集是一种用来管理元素分组情况的数据结构。(并查集是用树形结构实现的)
翻译:
三种操作:
1 x y:将元素x和y所在的集合合并(可用并查集)
2 x y:将元素x移到y所在的集合
3 x:输出元素x所在集合的元素的数量和元素的总和
分析:关键为如何进行2操作,如果x为所在集合的叶节点,可以直接用并查集把x归并到y所在的集合。但如果不是叶节点,直接把x归到y所在的集合,会把x所有的儿子也全都归并到y所在的集合。对于3的输出操作,并查集的根结点在路径压缩中是不变的,可以将两个信息维护在根结点上。
问题转化为了对于2操作,如何把x节点从集合中剥离出来,又不影响集合的结构。
解决方案:将每个节点都起一个编号:g[i]=i,表示节点i的编号为i,对于1操作,每次把节点所表示的编号进行并查集。对于2操作,把要去除的节点开一个新的编号,相当于这个节点进行了“分身”,真身留在原集合维护树的结构,分身出来归到y集合中。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m;
int f[100001],g[100001];
int num[100001],sum[100001];
void init()
{
for(int i=1; i<=n; i++) /*刚开始有n个集合*/
{
f[i]=g[i]=i;
sum[i]=i;//sum记录一个集合中元素的总和,num记录一个集合中元素的个数
num[i]=1;
}
}
int getf(int x)
{
if(f[x]==x)
return x;
else
{
f[x]=getf(f[x]);
return f[x];
}
}
void unite(int x,int y)
{
int t1=getf(x);
int t2=getf(y);
if(t1!=t2)
{
f[t1]=t2;
sum[t2]+=sum[t1];
num[t2]+=num[t1];
}
}
int main()
{
while(~scanf("%d %d",&n,&m))
{
int temp=n;
init();
int p,q;
while(m--)
{
int a;
scanf("%d",&a);//a的取值为1 2 3
if(a==1)
{
scanf("%d%d",&p,&q);//将元素p,q所在的集合合并
unite(g[p],g[q]);
}
if(a==2)
{
scanf("%d%d",&p,&q);//将元素p,移动到q所在的集合
int u=getf(g[p]),v=getf(g[q]);
if(u!=v)
{
sum[u]-=p;//把p从集合中去除
num[u]--;
temp++;
g[p]=temp;
f[temp]=temp;
sum[temp]=p;
num[temp]=1;
unite(g[p],g[q]);
}
}
if(a==3)
{
scanf("%d",&p);//输出p所在集合元素的数量和总和
int d=getf(g[p]);
printf("%d %d\n",num[d],sum[d]);
}
}
}
return 0;
}