题目描述
有一个划分为N列的星际战场,各列依次编号为1,2,…,N。
有N艘战舰,也依次编号为1,2,…,N,其中第i号战舰处于第i列。
有T条指令,每条指令格式为以下两种之一:
1、M i j,表示让第i号战舰所在列的全部战舰保持原有顺序,接在第j号战舰所在列的尾部。
2、C i j,表示询问第i号战舰与第j号战舰当前是否处于同一列中,如果在同一列中,它们之间间隔了多少艘战舰。
现在需要你编写一个程序,处理一系列的指令。
输入格式
第一行包含整数T,表示共有T条指令。
接下来T行,每行一个指令,指令有两种形式:M i j或C i j。
其中M和C为大写字母表示指令类型,i和j为整数,表示指令涉及的战舰编号。
输出格式
你的程序应当依次对输入的每一条指令进行分析和处理:
如果是M i j形式,则表示舰队排列发生了变化,你的程序要注意到这一点,但是不要输出任何信息;
如果是C i j形式,你的程序要输出一行,仅包含一个整数,表示在同一列上,第i号战舰与第j号战舰之间布置的战舰数目,如果第i号战舰与第j号战舰当前不在同一列上,则输出-1。
数据范围
N≤30000,T≤500000
输入样例:
4
M 2 3
C 1 2
M 2 4
C 4 2
输出样例:
-1
1
解题思路
首先考虑如何判断两艘战舰是否在处于同一列中:将某一个集合中的所有元素移入另一个集合中,很容易可以想到并查集,所以这一步我们可以采用并查集来解决。
接下来考虑如何计算同一列两艘战舰之间的战舰数量:并查集维护的是某个节点与根节点之间的关系,我们可以维护一个距离数组d,d[i]表示的是节点 i 到根节点的距离,那么两艘战舰(a、b)之间的的战舰数量
c
n
t
cnt
cnt 就为
c
n
t
=
∣
d
[
a
]
−
d
[
b
]
∣
−
1
,
a
≠
b
cnt = |d[a] - d[b]| - 1, a ≠ b
cnt=∣d[a]−d[b]∣−1,a=b
c
n
t
=
0
,
a
=
b
cnt = 0, a = b
cnt=0,a=b
那么问题就转化为如何维护 d 数组呢?因为是到根节点的距离,所以我们可以在每次插入的时候只修改根节点到新的根节点之间的距离,然后在每次路径压缩的时候再来修改其它节点到新的根节点之间的距离(具体见代码),而如何跟新集合a到集合b根节点的距离呢?需要引入size数组,size[i]用于记录集合中的节点数量,这里 i 必须为集合根节点的编号,因为我们只会对根节点的值进行修改,也只会用到根节点的值。
思路来源:AcWing 算法提高课
代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 30010;
int n, m;
int p[N], d[N], s[N];
int find(int x)
{
if (p[x] != x)
{
int nf = find(p[x]);
//更新节点到根节点的距离
d[x] += d[p[x]];
p[x] = nf;
}
return p[x];
}
int main()
{
cin >> m;
for (int i = 1; i < N; ++i) p[i] = i, s[i] = 1;
int a, b;
char op;
while (m--)
{
cin >> op >> a >> b;
if (op == 'M')
{
int fa = find(a), fb = find(b);
if (fa == fb) continue;
d[fa] = s[fb];
s[fb] += s[fa];
p[fa] = fb;
}
else
{
if (a == b) cout << 0 << '\n';
else
{
int fa = find(a), fb = find(b);
if (fa != fb) cout << -1 << '\n';
else cout << abs(d[a] - d[b]) - 1 << '\n';
}
}
}
return 0;
}