题意:
初始时,一共有n个元素的组合1,2,3....n
给出三个操作
1 p q:合并p,q所在的集合
2 p q:把p移动到q所在的集合
3 p:输出p所在的集合的元素的个数
思路:1,3相当简单,赤裸裸的并查集就好了
麻烦的是2,并查集是单向的,只知道父亲不知道儿子
所有如果i是叶子节点无所谓
如果是父亲节点就会十分的蛋疼:
你是到另一个集合里面去了,你的儿子们呢?
把i这个点复制一下(编号为cnt++),当作叶子节点扔到新集合里去
原来的i仅仅有作为集合编号的作用,可以认为是个空点 ,只是为了不让原来的并查集出现错误,防止他的儿子们找不到根节点
重新找一个节点,记录这个节点自己的信息,也就是sum = 他自己, num = 1;
注意:合并或者查询等的时候一定要合并id[i],因为原来的i只是一个空节点了,为了保证并查集不出错而存在的,在del函数的时候,传参
要穿本身,因为一会要用到本身,新的节点的sum是他本身,并且对id数组更新的时候,下标也是他本身
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 3*1e5+5;
int pre[maxn], id[maxn], num[maxn], sum[maxn], cnt;
void init(int n)
{
cnt = n;
for(int i = 1; i <= n; i++)
pre[i] = i, num[i] = 1, sum[i] = i, id[i] = i;
}
int Find(int x)
{
int r = x;
while(pre[r] != r) r = pre[r];
int i = x, j;
while(pre[i] != r)
{
j = pre[i];
pre[i] = j;
i = j;
}
return r;
}
void join(int x, int y)
{
int a = Find(x);
int b = Find(y);
if(a != b)
{
pre[b] = a;
sum[a] += sum[b];
num[a] += num[b];
}
}
void del(int x)
{
int r = Find(id[x]);
num[r]--;
sum[r] -= x;
cnt++;
pre[cnt] = cnt;
num[cnt] = 1;
sum[cnt] = x;
id[x] = cnt;
}
int main(void)
{
int n, m;
while(cin >> n >> m)
{
init(n);
while(m--)
{
int cmd, x, y;
scanf("%d", &cmd);
if(cmd == 1)
{
scanf("%d%d", &x, &y);
join(id[x], id[y]);
}
if(cmd == 2)
{
scanf("%d%d", &x, &y);
//注意要判断下是否在同一颗树上
if(Find(id[x]) != Find(id[y]))
{
del(x);
join(id[x], id[y]);
}
}
if(cmd == 3)
{
scanf("%d", &x);
int r = Find(id[x]);
printf("%d %d\n", num[r], sum[r]);
}
}
}
return 0;
}