题解:AcWing 238 银河英雄传说

【题目来源】

AcWing:238. 银河英雄传说 - AcWing题库

【题目描述】

有一个划分为 \(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\)

【输入样例】

4
M 2 3
C 1 2
M 2 4
C 4 2

【输出样例】

-1
1

【核心思想】

  1. 问题分析:给定 \(N\) 艘战舰初始各自成一列,以及 \(T\) 条指令(合并两列或查询两艘战舰是否在同一列及间隔数)。需要动态维护多列战舰的合并,并支持查询同一列中两艘战舰之间的间隔数量。这是一个带权并查集问题,关键在于维护每个战舰到所在列队首的相对距离。

  2. 算法选择

    • 带权并查集(Extended Union-Find):维护每个节点到根节点的距离,支持 \(O(\alpha(n))\) 的合并与查询
    • 路径压缩更新距离:在路径压缩时,累加父节点到根节点的距离,保持相对位置信息
    • 集合大小维护siz[x] 记录以 \(x\) 为根的列的战舰总数,用于合并时计算新根的距离
  3. 关键步骤

    • 初始化p[i] = isiz[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$(两艘战舰之间布置的战舰数目,不包括自身)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(T \cdot \alpha(N))\),其中 \(\alpha\) 为阿克曼函数反函数,近似常数。每次查找或合并操作均摊 \(O(\alpha(N))\)
    • 空间复杂度:\(O(N)\),并查集父节点数组、距离数组和集合大小数组
  5. 带权并查集的核心思想

    • 相对距离维护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
posted @ 2026-06-23 11:23  团爸讲算法  阅读(5)  评论(0)    收藏  举报