题意:初始有N个集合,分别为 1 ,2 ,3 .....n。有三种操件
1 p q 合并元素p和q的集合
2 p q 把p元素移到q集合中
3 p 输出p元素集合的个数及全部元素的和。
思路:并查集操作。1、3步比较容易实现,只要建立一个sum[],cnt[],记录每个结点相应值,和并时把值更新到根结点,输出时只要找到根结点输出其值即可。
但2操作有点麻烦,并查集没有删除操作。原先以为直接把p指向q的根就行了,但发现这是错的。如果p是叶子结点,那没问题,但如果是某个集合的根呢。我们只是要把这一个元素移掉,如果直接把p指向q的根,它的叶子结点也指向了。。。
1 p q 合并元素p和q的集合
2 p q 把p元素移到q集合中
3 p 输出p元素集合的个数及全部元素的和。
思路:并查集操作。1、3步比较容易实现,只要建立一个sum[],cnt[],记录每个结点相应值,和并时把值更新到根结点,输出时只要找到根结点输出其值即可。
但2操作有点麻烦,并查集没有删除操作。原先以为直接把p指向q的根就行了,但发现这是错的。如果p是叶子结点,那没问题,但如果是某个集合的根呢。我们只是要把这一个元素移掉,如果直接把p指向q的根,它的叶子结点也指向了。。。
换个思路,将p这个点对它所在的集合的“影响”将为0。然后再新开一个节点表示p这个节点就相当于把p从原来的集合中剥离出来了。因此相比原来的并查集,需要多一个id[p]数组表示点p现在的标号,原来的编号不变,如果p是根节点的话,相当于现在是原来集合的虚拟根节点。
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=200010;
int n,m,depth;
int pra[maxn],id[maxn],num[maxn];
long long sum[maxn];
void init()
{
depth=n;
for(int i=0;i<=n;i++)
{
pra[i]=id[i]=sum[i]=i;
num[i]=1;
}
}
int find(int x)
{
if(pra[x]==x)return x;
return pra[x]=find(pra[x]);
}
void unite(int p,int q)
{
int x=find(p);
int y=find(q);
pra[x]=y;
sum[y]+=sum[x];
num[y]+=num[x];
}
void move(int p)
{
int x=find(id[p]);
sum[x]-=p;
num[x]--;
id[p]=++depth;//新开一个节点
sum[id[p]]=p;
num[id[p]]=1;
pra[id[p]]=id[p];
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
int op,p,q;
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
for(int i=0;i<m;i++)
{
scanf("%d",&op);
if(op==1)
{
scanf("%d%d",&p,&q);
if(find(id[p])!=find(id[q]))
unite(id[p],id[q]);
}
else if(op==2)
{
scanf("%d%d",&p,&q);
if(find(id[p])!=find(id[q]))
{move(p);unite(id[p],id[q]);}
}
else
{
scanf("%d",&p);
int x=find(id[p]);
printf("%d %lld\n",num[x],sum[x]);
}
}
}
return 0;
}