题干:
有一个划分成N列的星际战场,各列依次编号为1,2,…,N。有N艘战舰,也依次编号为1,2,…,N,其中第i号战舰处于第i列。
有M条指令,每条指令格式为以下两种之一:
- M i j,表示让第i号战舰所在列的全部战舰保持原有顺序,接在第j号战舰所在列的尾部。
- C i j,表示询问第i号舰艇与第j号舰艇当前是否处在同一列中,如果在同一列中,他们之间隔了多少艘战舰。如果第i号战舰与第j号战舰当前不在同一列上,则输出-1。
N<=3000 M<=5*105。
样例输入 Sample Input
4
M 2 3
C 1 2
M 2 4
C 4 2
样例输出 Sample Output
-1
1
思路:一条“链"也是一棵树,只不过是树的特殊形态。因此可以把一列战舰看作一个集合,用并查集维护。最初,N个战舰构成N个独立的集合。
fa[x]表示在第x号战舰前面的那个战舰号
一个集合的代表就是位于最前边的战舰
让树的每条边带权值1,这样树上两点间的距离-1就是间隔的战舰数量。
在考虑路径压缩的情况下,额外建立一个数组d,d[x]记录战舰x与
fa[x]之间的边的权值。
在路径压缩时把x直接指向树根的同时,同时更新d[x]为x到树根的路径上所有边权之和。
对get函数稍加修改,就可以实现对d数组的维护:
int get(int x){
if(fa[x]==x) return x;
int root=get(fa[x]);
d[x]+=d[fa[x]];
}
当接收到一个 C x y指令时,分别执行get(x)和get(y)完成查询和路径压缩。若二者返回值相同,则说明x和y处在同一列中。d[x]和
d[y]差的绝对值减1,就是x和y之间间隔的战舰数量。
当接收到一个M x y指令时,把x的树根作为y的树根的子节点,连接边的权值应该是合并之前y集合的大小(因为根据题意y集合中的全部战舰都排在x之前),还需要一个size数组在每个树根上记录集合的大小。
void merge(int x,int y){
x=get(x);y=get(y);
fa[x]=y;
d[x]=size[y];
size[y]+=size[x];
}
完整代码:
#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;
}