题解:AcWing 238 银河英雄传说
【题目来源】
AcWing:238. 银河英雄传说 - AcWing题库
【题目描述】
有一个划分为 \(N\) 列的星际战场,各列依次编号为 \(1,2,…,N\)。
有 \(N\) 艘战舰,也依次编号为 \(1,2,…,N\),其中第 \(i\) 号战舰处于第 \(i\) 列。
有 \(T\) 条指令,每条指令格式为以下两种之一:
M i j,表示让第 \(i\) 号战舰所在列的全部战舰保持原有顺序,接在第 \(j\) 号战舰所在列的尾部。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\)。
【输入样例】
4
M 2 3
C 1 2
M 2 4
C 4 2
【输出样例】
-1
1
【核心思想】
-
问题分析:给定 \(N\) 艘战舰初始各自成一列,以及 \(T\) 条指令(合并两列或查询两艘战舰是否在同一列及间隔数)。需要动态维护多列战舰的合并,并支持查询同一列中两艘战舰之间的间隔数量。这是一个带权并查集问题,关键在于维护每个战舰到所在列队首的相对距离。
-
算法选择:
- 带权并查集(Extended Union-Find):维护每个节点到根节点的距离,支持 \(O(\alpha(n))\) 的合并与查询
- 路径压缩更新距离:在路径压缩时,累加父节点到根节点的距离,保持相对位置信息
- 集合大小维护:
siz[x]记录以 \(x\) 为根的列的战舰总数,用于合并时计算新根的距离
-
关键步骤:
- 初始化:
p[i] = i,siz[i] = 1,`d[i] = 0$(每艘战舰自成一列,到自身的距离为 0) - 带权查找(路径压缩):
- 递归查找根节点
u = find(p[x]) - 更新
d[x] += d[p[x]],使d[x]表示 \(x\) 到根节点的累计距离(到队首的战舰间隔数) p[x] = u路径压缩
- 递归查找根节点
- 合并指令
M a b(将 \(a\) 所在列接在 \(b\) 所在列尾部):pa = find(a),pb = find(b)- 若
pa != pb:d[pa] = siz[pb]:\(a\) 所在列的根节点到 \(b\) 所在列根节点的距离 = \(b\) 列的战舰总数siz[pb] += siz[pa]:\(b\) 列战舰总数增加p[pa] = pb:将 \(a\) 列的根挂到 \(b\) 列的根下面
- 查询指令
C a b:pa = find(a),pb = find(b)- 若
pa != pb:输出-1(不在同一列) - 若
pa == pb:间隔数 = `abs(d[a] - d[b]) - 1$(两艘战舰之间布置的战舰数目,不包括自身)
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(T \cdot \alpha(N))\),其中 \(\alpha\) 为阿克曼函数反函数,近似常数。每次查找或合并操作均摊 \(O(\alpha(N))\)
- 空间复杂度:\(O(N)\),并查集父节点数组、距离数组和集合大小数组
-
带权并查集的核心思想:
- 相对距离维护:
d[x]表示 \(x\) 到根节点的距离,通过路径压缩累加更新,保持相对位置信息不丢失 - 合并时设置距离:将集合 \(A\) 接在集合 \(B\) 尾部时,\(A\) 的根到 \(B\) 的根的距离 = \(B\) 的集合大小,保证 \(A\) 中所有元素到 \(B\) 根的距离正确
- 集合大小传递:合并后更新
siz[pb],为后续合并提供正确的距离基准 - 查询时计算间隔:同一集合内两节点的间隔 = 两者到根的距离之差的绝对值减 1
- 适用于需要维护相对位置/距离的集合合并与查询问题,如队列合并、食物链、银河英雄传说等
- 相对距离维护:
【算法标签】
并查集
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 30005; // 最大战舰数量(N 最大为 30000)
int m; // 指令数量
int p[N], siz[N], d[N]; // p: 并查集父节点; siz[x]: 以 x 为根的列的战舰总数; d[x]: x 到根节点的战舰间隔数
// 带权并查集查找(路径压缩),同时维护到根节点的距离
int find(int x)
{
if (p[x] != x)
{
int u = find(p[x]); // 递归查找根节点
d[x] += d[p[x]]; // 更新 x 到根节点的距离:累加父节点到根节点的距离
p[x] = u; // 路径压缩:将 x 直接指向根节点
}
return p[x]; // 返回根节点
}
int main()
{
cin >> m; // 读入指令数量
for (int i = 1; i < N; i++) // 初始化并查集
{
p[i] = i; // 每个战舰自成一个集合(初始时每列一艘战舰)
siz[i] = 1; // 每列初始战舰数为 1
}
while (m--) // 依次处理每条指令
{
char op; // 指令类型:'M' 合并,'C' 查询
int a, b; // 涉及的两艘战舰编号
cin >> op >> a >> b;
if (op == 'M') // 合并指令:将 a 所在列接在 b 所在列的尾部
{
int pa = find(a), pb = find(b); // 查找 a 和 b 的根节点
if (pa != pb) // 若不在同一列才进行合并
{
d[pa] = siz[pb]; // a 所在列的根节点到 b 所在列根节点的距离 = b 列的战舰数
siz[pb] += siz[pa]; // b 列的战舰总数增加(加上 a 列的战舰数)
p[pa] = pb; // 将 a 列的根挂到 b 列的根下面
}
}
else // 查询指令:询问 a 和 b 是否在同一列及间隔数
{
int pa = find(a), pb = find(b); // 查找 a 和 b 的根节点
if (pa != pb) // 若不在同一列
cout << -1 << endl; // 输出 -1
else // 若在同一列
// 间隔数 = |d[a] - d[b]| - 1(两艘战舰之间布置的战舰数目,不包括自身)
cout << max(0, abs(d[a] - d[b]) - 1) << endl;
}
}
return 0;
}
【运行结果】
4
M 2 3
C 1 2
-1
M 2 4
C 4 2
1
浙公网安备 33010602011771号