带权并查集。有N
个龙珠和N
个城市,刚开始是按照编号一一对应的,后来有些龙珠跑到别的城市去了。给你Q
句话,分为事实和查询,事实是T A B : All the dragon balls which are in the same city with A have been transported to the city the Bth ball in.
,这句话的理解就是和编号为A
的龙珠在同一个城市的所有龙珠(包括A
)都去编号为B
的龙珠所在的城市。 以上事实并不涉及城市编号。查询就是给一个龙珠的编号,问你属于哪个城市,以及这个城市总共多少龙珠,还有这个龙珠折腾了多少次。
每个城市都有一个堆,堆内的元素都属于该城市。设一个数组city[]
表示每个点所属的城市编号,至于每个堆的总数可以用堆顶点的-pre[]
表示。而关键的转移次数cnt[]
需要在find
函数里面从上而下累加得到。
city[]
和cnt[]
都要在find
函数的递归回溯过程中从上而下的传递,city[]
很简单,这个值在一个堆里不可能变。而cnt[]
需要累加(这个值不等于该结点到根节点的边的个数!)。
至于cnt[]
这么传递为什么正确,有点难解释。另外突然发现一个并查集的性质,当某个点不再是根节点了以后,这个点的以下的链不可能再增长了。
另外,这道题要给一个查询作一次输出,为什么?看样例就明白了。
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
const int MAXN = 10005;
int N, Q, T;
int pre[MAXN];
int cnt[MAXN];
int city[MAXN];
void init()
{
memset(pre, -1, sizeof pre);
memset(cnt, 0, sizeof cnt);
for (int i = 1; i <= N; i++) city[i] = i;
}
int f(int x)
{
if (pre[x] < 0) return x;
int t = f(pre[x]);
cnt[x] += cnt[pre[x]]; //
city[x] = city[pre[x]];
return pre[x] = t;
}
void u(int a, int b)
{
int f1 = f(a);
int f2 = f(b);
if (f1 != f2)
{
pre[f2] += pre[f1];
pre[f1] = f2;
//cnt[f1]++;
cnt[f1] = 1; // 这两句等价
city[f1] = city[f2];
}
}
int main()
{
char c;
int a, b;
int x, y, z;
scanf("%d", &T);
for (int i = 1; i <= T; i++)
{
printf("Case %d:\n", i);
scanf("%d%d", &N, &Q);
init();
for (; Q--;)
{
getchar(); // 吸收换行符
scanf("%c", &c);
if (c == 'T')
{
scanf("%d%d", &a, &b);
u(a, b);
}
else
{
scanf("%d", &a);
int f0 = f(a);
x = city[a];
y = -pre[f0];
z = cnt[a];
printf("%d %d %d\n", x, y, z);
}
}
}
return 0;
}