题意:
划分为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也有某种确定的关系。
而本题所要维护的关系有两个,一个是是否在同一集合中,另一个则是该点距离根节点 (即每列第一个元素) 的距离,如果维护出了这个距离,那么任意两点间的距离问题也就迎刃而解了。
所以我们只需要给每一个节点建立一个 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;
}