银河英雄传说 - 带权并查集详解Plus

🌌 银河英雄传说 - 带权并查集详解

问题背景

在银河英雄传说中,需要高效处理战舰队列的合并操作和距离查询。让我们一步步理解如何用带权并查集解决这个问题!

核心思路:维护三个关键信息

int parent[MAXN]; // 记录每个战舰的上级(父节点)
int d[MAXN];      // 记录战舰到父节点的距离(间隔战舰数)
int size[MAXN];   // 记录每个战舰队列的长度

初始化

for (int i = 1; i <= $30000$; i++) {
    parent[i] = i; // 每个战舰初始独立,自己是自己的上级
    d[i] = $0$;     // 自己到自己的距离是0
    size[i] = $1$;  // 每个队列初始只有1艘战舰
}

核心操作解析

1. 查找根节点(带路径压缩)

int find(int x) {
    if (parent[x] != x) {
        int root = find(parent[x]); // 先递归找到根节点
        d[x] += d[parent[x]];      // 更新当前战舰到根的距离
        parent[x] = root;           // 路径压缩:把上级直接设为根节点
    }
    return parent[x];
}

📝 通俗解释
假设你是一个士兵(战舰),你想找到最高指挥官(根节点)。你问班长,班长问排长…直到找到将军。
找到后,将军命令:"以后直接向我汇报!"于是你记下将军的位置,并计算自己离将军的距离(原班长离将军的距离+++你离班长的距离)。

2. 合并队列(M i j

void merge(int i, int j) {
    int root_i = find(i); // 找到$i$的最高指挥官
    int root_j = find(j); // 找到$j$的最高指挥官
    
    parent[root_i] = root_j;  // 让$i$的指挥官归顺$j$的指挥官
    d[root_i] = size[root_j]; // $i$指挥官离新指挥官的距离 $=$ $j$队列原长度
    size[root_j] += size[root_i]; // 新队列长度增加
}

📝 通俗解释
将军AAA带领100100100人的队伍要加入将军BBB(有200200200人的队伍)。将军BBB说:“让你的队伍排在我的队伍最后面”。
于是将军AAA离将军BBB的距离就是200200200(因为BBB原队伍有200200200人),新队伍总长度变成300300300人。

3. 查询距离(C i j

int query(int i, int j) {
    if (find(i) != find(j)) // 不在同一队列
        return $-1$;
    
    // $|d[i] - d[j]| - 1$
    return abs(d[i] - d[j]) - $1$; 
}

📝 通俗解释
士兵AAA站在离将军333步的位置(前面有333人)
士兵BBB站在离将军555步的位置(前面有555人)
AAABBB之间间隔的人数就是 ∣3−5∣−1=1|3-5|-1 = 1∣35∣1=1

图解样例分析

初始状态

战舰111222333444
队列[1][1][1][2][2][2][3][3][3][4][4][4]
距离000000000000
大小111111111111

执行 M 2 3

222的队列(2)(2)(2)接到333的队列(3)(3)(3)后面:

2
3
  • 更新
    • d[2] = size[3] = 1
    • size[3] = 1+1 = 2
  • 当前队列
    [1][1][1] [3,2][3,2][3,2] [4][4][4]

执行 C 1 2

  • 111在队列[1][1][1]222在队列[3,2][3,2][3,2]
  • 不同队列 → 输出 −1-11

执行 M 2 4

222所在的队列[3,2][3,2][3,2]接到444的队列[4][4][4]后面:

2
3
4
  • 更新
    • d[3] = size[4] =1$
    • size[4] = 1+2 = 3
  • 注意:此时222的距离动态更新:
    d[2] = d[2] + d[3] = 1 + 1 = 2

执行 C 4 2

  • 444222在同一队列[4,3,2][4,3,2][4,3,2]
  • d[4] = 0 (444是根节点)
  • d[2] = 2
  • 距离计算∣0−2∣−1=1|0-2|-1 = 1∣02∣1=1 → 输出 111

完整代码实现

#include <iostream>
#include <cstdlib> // 用于abs函数
using namespace std;

const int MAXN = $30010$;

int parent[MAXN];
int d[MAXN];
int size[MAXN];

// 查找根节点(带路径压缩和距离更新)
int find(int x) {
    if (parent[x] != x) {
        int root = find(parent[x]);
        d[x] += d[parent[x]];
        parent[x] = root;
    }
    return parent[x];
}

int main() {
    // 初始化
    for (int i = $1$; i < MAXN; i++) {
        parent[i] = i;
        d[i] = $0$;
        size[i] = $1$;
    }
    
    int T;
    cin >> T;
    
    while (T--) {
        char op;
        int i, j;
        cin >> op >> i >> j;
        
        if (op == 'M') { // 合并命令
            int root_i = find(i);
            int root_j = find(j);
            parent[root_i] = root_j;
            d[root_i] = size[root_j];      // 更新距离
            size[root_j] += size[root_i];  // 更新队列大小
        } else { // 查询命令
            if (find(i) != find(j)) {
                cout << $-1$ << endl;        // 不同队列
            } else {
                // 计算距离差并减去1
                cout << abs(d[i] - d[j]) - $1$ << endl;
            }
        }
    }
    
    return $0$;
}

关键技巧总结

111. 路径压缩:查找时压平路径,保证后续查询高效
222. 距离累加:递归过程中动态计算到根节点的距离
333. 实时更新:合并时立即更新队列大小
444. 距离计算:使用公式∣d[i]−d[j]∣−1|d[i]-d[j]|-1d[i]d[j]1计算间隔战舰数
555. 高效处理:能在0.50.50.5秒内处理500000500000500000次操作

这个算法完美解决了银河战舰的动态调度问题,是带权并查集的经典应用!🌟

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值