HDU 3635 Dragon Balls

版权声明:本人QQ 249712949 欢迎交流算法(请备注)。 https://blog.csdn.net/coord_/article/details/88674862

传送门

带权并查集。有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;
}

猜你喜欢

转载自blog.csdn.net/coord_/article/details/88674862