题解:P1196 [NOI2002] 银河英雄传说

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;
}

posted @ 2025-10-21 16:34  Noivelist  阅读(5)  评论(0)    收藏  举报