P1196 [NOI2002] 银河英雄传说
这是一道绿题
核心考察点只有一个: 那就是带权并查集
\(\mathcal{Part\ I}\)
我们检查题意不难发现这道题的要求无非两个:
$\ \ $ 1 ) 维护多个链的不断合并,但是以链中某节点作为索引
$\ \ \ \ \ \ \ $ 所以我们考虑并查集
$\ \ $ 2 ) 查询两个节点是否在某个链中,并且求出他们之间的权值差
$\ \ \ \ \ \ \ $ 因为要查询权值,而且并查集没有什么很好的伴生算法,只能使用带权并查集
\(\mathcal{Part\ II}\)
实现过程的话。。。其实就是标程
讲一遍吧(
$\ $
初始化并查集,这里分两步:
合并的时候,我们把第一集合的祖先的祖先插到第二集合的祖先上
这时关键的点在于不用更新每一个第一集合的点
我们退而求其次,只更新第一集合原祖先到队头的距离和当前总集合的大小
最后因为还要路径压缩,在路径压缩的时候顺下来就好了
查询的时候,我们查询的时候因为也要调用find函数两次,所以也可以帮忙顺一遍并查集的权值
这里的代码框架的好处就在于,不管我们要进行哪些操作,我们的更新写在find里,导致我们不用担心合并-查询的数据崭新度
而我们查询的操作极其简单,查询两个节点的祖先是否在同一个
如果不是,输出 -1
如果是,那么输出他们两个在链上的权值差-1就好了,这个-1是因为要计算他们之间隔了多少个
非常愉快,这个代码就写完了
我这边单独把 find
贴出来方便理解
int Find(int x) {if (x == fa[x]) return x; //回溯操作int k = fa[x]; //临时变量fa[x] = Find(fa[x]); //路径压缩s[x] += s[k]; //更新权值大小b[x] = b[fa[x]]; //更新集合大小return fa[x]; //传递
}
\(\LARGE{\mathcal{CODE}}\)
#include <bits/stdc++.h>using namespace std;const int N = 3e4 + 10;
int T, fa[N], s[N], b[N];int Find(int x) {if (x == fa[x]) return x;int k = fa[x];fa[x] = Find(fa[x]);s[x] += s[k];b[x] = b[fa[x]];return fa[x];
}void SolveM() {int x, y;cin >> x >> y;int dx = Find(x);int dy = Find(y);fa[dx] = dy;s[dx] += b[dy];b[dx] = b[dy] = b[dy] + b[dx];
}void SolveC() {int x, y;cin >> x >> y;int dx = Find(x);int dy = Find(y);if (dx != dy) {cout << "-1" << endl;return;}cout << abs(s[x] - s[y]) - 1 << endl;
}int main() {ios::sync_with_stdio(false); cin.tie(nullptr);cout.tie(nullptr);cin >> T;for (int i = 1; i <= 30000; i++) {fa[i] = i;s[i] = 0;b[i] = 1;}while (T--) {char ch;cin >> ch;if (ch == 'M') {SolveM();}if (ch == 'C') {SolveC();}}return 0;
}