【带权并查集经典例题】银河英雄传说【同POJ 1988 cube stacking】

题意:

     划分为N列的星际战场,各列编号为1,2,...,N。初始每列一艘战舰,第 i 列战舰位于第 i 列。

     M条指令,每条指令为以下两种之一:

1.M i j,表示让第 i 号战舰所在列的全部战舰保持原有顺序,接在第 j 号战舰所在列的尾部。

2.C i j,表示询问第 i 号战舰与第 j 号战舰当前是否在同一列中,如果在同一列中,它们之间间隔了多少艘战舰。

     N <= 30000,M <= 5*1e5

思路:

     对于本题,我们先进行一个简化,如果本题没有问你 i 号战舰和 j 号战舰之间间隔了多少艘战舰,只问你是不是在同一列,那该怎么做呢?

     很明显,如果没问你中间隔了多少战舰,那么本题就是一道并查集水题。但是现在问了你两艘战舰之间的距离,那该怎么做呢?

     我们可以这样思考,并查集的本质就是动态维护许多具有传递性的关系,具体来说就是如果A和B有某种关系,B和C有某种关系,那么A和C也有某种确定的关系。

     而本题所要维护的关系有两个一个是是否在同一集合中另一个则是该点距离根节点 (即每列第一个元素) 的距离,如果维护出了这个距离,那么任意两点间的距离问题也就迎刃而解了。

扫描二维码关注公众号,回复: 2992099 查看本文章

     所以我们只需要给每一个节点建立一个 D[x] 数组,表示 x 点距离 x 所在列的根节点的距离。这个距离在维护两点连通性时一同进行维护,可见下图代码:

     因为此处在求根节点的时候,find是一个递归函数,不是一个递推函数,因此函数会不断进行加深搜索,直到返回根节点,此时的 f 就是点 k 的根节点。 此时 dis[k] 加的是 dis[p[k]],而不是 dis[f],因为函数不断加深搜索,前面的 dis[x] 都已经更新过了,因此此处只需加上 dis[p[k]],即可将 dis[k] 更新成功。

     通过本题,我们可以更加深刻地明白所谓并查集,就是一个高效判断图中两点是否连通,并且维护两个连通点之间的信息的方法。

总结:

     并查集简单来说就是一个动态维护节点之间传递性信息的数据结构。而带权并查集,只不过是记录节点之间传递性信息的时候,同时记录该点距离根节点的权值,从而实现题目目的。

代码:【取自《算法竞赛进阶指南》书中】

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
using namespace std;
int n;
int p[30010], dis[30010], size[30010];

int find(int k)
{
	if (p[k] != k)
	{
		int f = find(p[k]);
		dis[k] += dis[p[k]];
		p[k] = f;
	}
	return p[k];
}

int main()
{
	for (int i = 1; i <= 30000; i++)
	{
		size[i] = 1;
		p[i] = i;
	}
	scanf("%d", &n);
	while (n--)
	{
		char s[2];
		int x, y;
		scanf("%s", s);
		scanf("%d%d", &x, &y);
		int fi = find(x), fj = find(y);
		if (s[0] == 'M')
		{
			p[fi] = fj;
			dis[fi] = size[fj];
			size[fj] += size[fi];
		}
		else
		{
			if (fi != fj)  printf("-1\n");
			else printf("%d\n", abs(dis[x] - dis[y]) - 1);
		}
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41552508/article/details/82152859