题意:模拟三种操作
思路:按题意模拟即可,麻烦的将p移动到q的集合,不能直接将p的父结点改为q的父结点,因为p可能是某个集合的根节点。解决的方法是不移动p结点,而维护集合的个数和集合元素和的关系,如果将p加入q的集合,则另外增加一个新的结点加入q的集合,而原来的p结点就变成了一个虚拟结点,对结果没有影响。所以开辟一个id数组记录改变后的结点的位置,使用时都从id数组中取数。
#include<iostream>
#include<cstdio>
#include<string.h>
#include<stack>
#include<set>
#include<map>
#include<vector>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 1000005;
int n, m;
int P[maxn];
int Sum[maxn]; //集合的和
int Tot[maxn]; //集合的个数
int id[maxn]; //id[i]代表结点i当前的位置
int pos;
void maketree()
{
for (int i = 0; i < maxn; i++)
{
P[i] = i;
Sum[i] = i;
Tot[i] = 1;
id[i] = i;
}
pos = n;
}
int Find(int x)
{
if (x != P[x])
return P[x] = Find(P[x]);
return P[x];
}
//将y连接到x上
void unite(int x, int y)
{
int p1 = Find(x);
int p2 = Find(y);
if (p1 != p2)
{
P[p2] = p1;
Sum[p1] += Sum[p2];
Tot[p1] += Tot[p2];
}
}
//由于是根节点时不能直接移动,所以新开辟了一个id数组记录数字i最后的位置
//将数字i移动到集合S
void Move(int i, int S)
{
int p1 = Find(id[i]);
int p2 = Find(S);
if (p1 != p2)
{//原结点变成了虚拟根结点,对结果无影响
Tot[p1]--;
Tot[p2]++;
Sum[p1] -= i;
Sum[p2] += i;
id[i] = ++pos;
P[pos] = p2; //给结点i新分配一个结点下标,放在原数组的后面
}
}
int main()
{
while (scanf("%d %d", &n, &m) != EOF)
{
maketree();
for (int i = 0; i < m; i++)
{
int c1, p, q;
scanf("%d %d", &c1, &p);
if (c1 == 1)
{
scanf("%d", &q);
unite(id[q], id[p]);
}
else if (c1 == 2) //不能直接让p指向新的集合(有可能时根节点)
{
scanf("%d", &q);
Move(p, id[q]);
}
else
{
int root = Find(id[p]);
printf("%d %d\n", Tot[root], Sum[root]);
}
}
}
return 0;
}