题目
多组数据,n个数(n<=1e5),初始是n个单元素集,{1},{2},...,{n},
m(m<=1e5)次操作,操作分三种
1 p q 合并p和q所在集合,如果已在同一集合中,则忽略
2 p q 把p移动到q所在集合,如果已在同一集合中,则忽略
3 p 询问p所在元素个数及所有元素之和
对于每条类型3的指令,输出p所在元素个数及元素之和
思路来源
https://blog.csdn.net/u013447865/article/details/39717737
题解
1和3都是常规的并查集操作,考虑2怎么维护
如果真的删除这个元素的话,需要考虑子节点的影响,
如果真的删掉的话,由于按秩合并可能还会到这个点,从而影响答案
所以并不真的删掉这个点x,而是将x的根root处减去x对于大小以及和的贡献,
记录id[x]映射的点号,新开一个点x用于放其大小及和,再把x挂到y所在的树根上去
诶感觉不判2操作在同一集合中的时候,这种写法好像没毛病啊,
然而就会WA,不知怎么回事……
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;
int n,m,op,u,v,par[N],sz[N],id[N],c;
ll sum[N];
void init(int n){
for(int i=1;i<=n;++i){
par[i]=sum[i]=id[i]=i;
sz[i]=1;
}
c=n;
}
int find(int x){
return par[x]==x?x:par[x]=find(par[x]);
}
void chg(int x){
int fa=find(id[x]);
sum[fa]-=x;
sz[fa]--;
id[x]=++c;
par[c]=c;
sum[c]=x;
sz[c]=1;
}
void unite(int x,int y){
x=find(x),y=find(y);
if(x==y)return;
par[y]=x;
sz[x]+=sz[y];
sum[x]+=sum[y];
}
int main(){
while(~scanf("%d%d",&n,&m)){
init(n);
for(int i=1;i<=m;++i){
scanf("%d",&op);
if(op==1){
scanf("%d%d",&u,&v);
unite(id[u],id[v]);
}
else if(op==2){
scanf("%d%d",&u,&v);
if(find(id[u])==find(id[v])){//不加这个会WA是怎么回事
continue;
}
chg(u);
unite(id[u],id[v]);
}
else{
scanf("%d",&u);
v=find(id[u]);
printf("%d %lld\n",sz[v],sum[v]);
}
}
}
return 0;
}