题意:有三种操作:
1 p q:合并元素p和q所在集合
2 p q:把元素p移动到q所在集合
3 p :输出p所在集合元素个数和该集合所有元素之和
分析:这道题考察了并查集的删除操作以及统计并查集元素和, 对于每一个集合,我们只需要考虑根节点root号位置,注意find查找到的是根节点的位置,实际根节点有可能不在这里,用cnt[root]和sum[root]来代表该集合元素个数与元素之和,并且在每一次操作时进行更新,初始时k号节点就在k号位置
下面重点讲讲删除操作,刚开始我的写法是像链表那样,把i的子节点与i的父节点相连,但是这样会超时。
我们用id[i]来代表节点i实际的位置,意思就是比如id[2]=2说明节点2在2号位置,id[2]=6说明节点2在6号位置,每次删除节点,我们可以新建一个比n大的节点序号tot,然后使id[i]=tot, 再合并id[i]和另一个集合。比如n=5,tot=6,i=2,id[i]=6 这样下一次我们再找2号的根节点时,我们找的是6号的根节点,也就是2号所属的当前集合的根节点。
这种做法无论删除的店是不是根节点都是成立的
#include<iostream>
#include<cstdio>
#include<cstring>
#define MAXN 100005
using namespace std;
int pa[MAXN];
long long cnt[MAXN], sum[MAXN];//分别代表集合元素个数和元素总和
int id[MAXN];//id[i]代表i现在所处的位置,i没被删除时id[i]=i,i被删除移动后id[i]=tot>n
int n, m;
int k, p, q;
int find(int x){
return pa[x]==x? pa[x]=x:find(pa[x]);
}
int main(){
while(~scanf("%d%d", &n, &m)){
int tot = n;
for(int i = 1; i<=n; i++){
pa[i] = i;
cnt[i] = 1;
sum[i] = i;
id[i] = i;
}
for(int i = 0; i<m; i++){
scanf("%d", &k);
if(k==1){
scanf("%d%d", &p, &q);
int root1 = find(id[p]), root2 = find(id[q]);//要找当前的位置,所以是id
if(root1==root2) continue;
pa[root1] = root2;
sum[root2]+=sum[root1];
cnt[root2]+=cnt[root1];
}
if(k==2){
scanf("%d%d", &p, &q);
int root1 = find(id[p]), root2 = find(id[q]);
if(root1==root2) continue;
id[p] = ++tot;//创建一个新节点,并且用新节点的值作为结点p的新位置
pa[id[p]] = pa[id[q]];
sum[root2]+=p;
cnt[root2]++;
sum[root1]-=p;
cnt[root1]--;
}
if(k==3){
scanf("%d",&p);
int root = find(id[p]);
printf("%lld %lld\n", cnt[root], sum[root]);
}
}
}
return 0;
}