这是一道考察并查集的路径压缩的题。题意:在悟空的世界,有N个龙珠和N个城市(编号从1到N),神龙最开始把每颗龙珠都放在对应编号的城市。悟空要去收集龙珠,但是这些龙珠有时候是会被转移的。你需要告诉悟空一些有关龙珠的信息才行。现在又T组测试,每组测试都有一个N(龙珠和城市的数量)和Q(操作行为的数量),操作行为有两种:
T A B,将编号为A的龙珠所在城市的所有龙珠转移到编号为B的龙珠所在的城市,两个城市不同
Q A,悟空要知道X(龙珠A所在城市的编号),Y(编号为X的城市里的龙珠数)以及Z(编号为A的龙珠转移的次数)
解题思路:首先根据每个龙珠的编号我们都需要知道3个信息,所在城市的编号,所在城市的龙珠数以及该龙珠被转移过的次数。首先我们分析一下T操作,首先两个城市一定不相同,那么将所有龙珠都移动到另一个城市时,肯定有一个龙珠本来就在那个城市且一次都没有被移动过,这个龙珠就是根节点。那么同理,根节点龙珠肯定从来没被移动过而且不会出现把这个城市的所有龙珠全部移动到空城市这种情况。有了这样的规律,我们可以通过维护根节点所在城市龙珠数来查找这个城市每个龙珠的X和Y。至于Z,路径压缩时我们要把对应节点压缩到根节点之下,对应的Z也要进行同步更新,每次移动时我们只将被移动的城市的根节点的移动次数+1,那么当路径压缩时沿途节点的移动次数之和(当然也包括待压缩节点本身)就是这个节点真正的被移动次数。
接下来是解题代码:
#include <stdio.h>
#include <stdlib.h>
#define N 10001
int bleg[N]; //存储当前节点的父节点
int tran[N]; //存储被移动的次数
int city[N]; //存储所在的城市
int bnum[N]; //存储在同一地点的龙珠总数
int t;
int n, m;
void Init(); //初始化
int Find(int x); //并查集查找
void Union(int x, int y); //并查集合并,也就是T操作
void Q(int x);
int main()
{
int tn = 1;
int i, a, b;
char ch;
scanf("%d", &t);
while (t--)
{
scanf("%d %d", &n, &m);
Init();
printf("Case %d:\n", tn++);
for (i=0; i<m; ++i)
{
scanf(" %c", &ch);
if (ch == 'T')
{
scanf("%d %d", &a, &b);
Union(a, b);
}
else
{
scanf("%d", &a);
Q(a);
}
}
}
return 0;
}
void Init() //初始化
{
int i;
for (i=1; i<=n; ++i)
{
bleg[i] = city[i] = i; //初始化父节点和所在城市
tran[i] = 0;
bnum[i] = 1;
}
return;
}
int Find(int x) //并查集查找
{
int y = x;
int z;
int ntran = 0;
while (y != bleg[y])
{
ntran += tran[y]; //存储沿途节点的被移动次数总和
y = bleg[y];
}
while (x != bleg[x])
{
z = bleg[x];
ntran -= tran[x]; //为下个节点更新移动次数做准备
bleg[x] = y; //压缩到根节点之下
tran[x] += ntran; //移动次数同步更新
x = z;
}
return y;
}
void Union(int x, int y) //并查集合并,也就是T操作
{
int fx = Find(x);
int fy = Find(y);
bleg[fx] = fy;
++tran[fx]; //被移动的根节点的被移动次数加1
bnum[fy] += bnum[fx]; //更新城市龙珠数
return;
}
void Q(int x)
{
int fx = Find(x);
printf("%d %d %d\n", city[fx], bnum[fx], tran[x]);
return;
}