🌌 银河英雄传说 - 带权并查集详解
问题背景
在银河英雄传说中,需要高效处理战舰队列的合并操作和距离查询。让我们一步步理解如何用带权并查集解决这个问题!
核心思路:维护三个关键信息
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人)
AAA和BBB之间间隔的人数就是 ∣3−5∣−1=1|3-5|-1 = 1∣3−5∣−1=1人
图解样例分析
初始状态
战舰 | 111 | 222 | 333 | 444 |
---|---|---|---|---|
队列 | [1][1][1] | [2][2][2] | [3][3][3] | [4][4][4] |
距离 | 000 | 000 | 000 | 000 |
大小 | 111 | 111 | 111 | 111 |
执行 M 2 3
将222的队列(2)(2)(2)接到333的队列(3)(3)(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-1−1
执行 M 2 4
将222所在的队列[3,2][3,2][3,2]接到444的队列[4][4][4]后面:
- 更新:
d[3] = size[4] =1$
size[4] = 1+2 = 3
- 注意:此时222的距离动态更新:
d[2] = d[2] + d[3] = 1 + 1 = 2
执行 C 4 2
- 444和222在同一队列[4,3,2][4,3,2][4,3,2]
d[4] = 0
(444是根节点)d[2] = 2
- 距离计算:∣0−2∣−1=1|0-2|-1 = 1∣0−2∣−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]|-1∣d[i]−d[j]∣−1计算间隔战舰数
555. 高效处理:能在0.50.50.5秒内处理500000500000500000次操作
这个算法完美解决了银河战舰的动态调度问题,是带权并查集的经典应用!🌟