【并查集】连通块中点的数量(并查集)

连通块中点的数量(并查集)

题目传送门https://www.lanqiao.cn/problems/19873/learning/


一、题目重述

给定一个初始包含 n 个点(编号 1n)的无向图,初始时没有边。需要进行 m 次操作,操作分为三种:

  1. C a b:在点 a 和点 b 之间连一条边(ab 可能相同)。
  2. Q1 a b:查询点 a 和点 b 是否在同一个连通块中。
  3. Q2 a:查询点 a 所在连通块中点的数量。

要求高效处理这些操作,并输出相应的查询结果。


二、题目分析

  • 操作类型:涉及动态的连通性维护(加边、查询连通性、查询连通块大小)。
  • 数据范围nm 都是 1e5 级别,需要 O(α(n)) 或近似 O(1) 的算法。
  • 关键点:快速合并集合、查询集合代表元、维护集合大小。

三、解题思路

这道题是典型的并查集(Disjoint Set Union, DSU)问题,适合用路径压缩按秩合并优化,确保高效处理合并和查询操作。


四、算法讲解

1. 并查集(DSU)

  • 用途:高效管理动态连通性(合并集合、查询是否连通)。
  • 核心操作
    • find(x):找 x 的根(代表元),同时路径压缩优化。
    • union(a, b):合并 ab 所在集合,按秩合并优化。
  • 优化
    • 路径压缩find(x) 时直接让 x 指向根,减少后续查询时间。
    • 按秩合并:总让小树合并到大树上,减少树高。

2. 维护连通块大小

  • 额外开一个数组 siz,记录每个根节点对应的连通块大小。
  • 合并时更新 sizsiz[大根] += siz[小根]

3. 例子

  • 初始:fa = [1,2,3,4,5], siz = [1,1,1,1,1]
  • C 1 2:合并 1 和 2,fa = [2,2,3,4,5], siz = [1,2,1,1,1]
  • Q1 1 2find(1) = find(2) = 2Yes
  • Q2 1siz[find(1)] = siz[2] = 2 → 输出 2

五、代码实现

#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
int fa[N];  // 并查集父节点数组
int n, m;

// 路径压缩的find函数
int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}

vector<int> siz(N, 1);  // 记录每个连通块的大小,初始为1

// 合并函数(按秩合并)
void uni(int a, int b) {
    if (a == b) return;  // 相同点无需合并
    a = find(a), b = find(b);
    if (a == b) return;  // 已在同一连通块
    if (siz[a] > siz[b]) swap(a, b);  // 确保a是小树
    fa[a] = b;          // 小树合并到大树
    siz[b] += siz[a];   // 更新连通块大小
}

int main() {
    // 初始化并查集
    for (int i = 1; i < N; i++) fa[i] = i;

    cin >> n >> m;
    int a, b;
    while (m--) {
        string op;
        cin >> op;
        if (op == "Q1") {  // 查询连通性
            cin >> a >> b;
            if (a == b) {   // 相同点直接Yes
                cout << "Yes" << "\n";
                continue;
            }
            a = find(a), b = find(b);
            cout << (a == b ? "Yes" : "No") << "\n";
        } else if (op == "Q2") {  // 查询连通块大小
            cin >> a;
            cout << siz[find(a)] << "\n";
        } else {  // 合并操作
            cin >> a >> b;
            uni(a, b);
        }
    }
    return 0;
}

六、代码重点细节解释

  1. 路径压缩find(x) 函数通过递归直接让 x 指向根,降低后续查询复杂度。
  2. 按秩合并:通过 siz 数组比较树大小,确保小树合并到大树,避免退化。
  3. 初始化fa[i] = isiz[i] = 1 是并查集的标准初始化。
  4. 查询处理Q1Q2 直接调用 findsiz 数组,保证 O(1) 查询。

七、复杂度分析

  • 时间复杂度
    • finduni 操作均摊 O(α(n))(近似常数时间)。
    • 总复杂度:O(m α(n)),轻松通过 1e5 数据。
  • 空间复杂度O(n),用于存储 fasiz 数组。

八、关键点

  1. 并查集优化:路径压缩 + 按秩合并缺一不可。
  2. 维护额外信息siz 数组动态记录连通块大小。
  3. 特判自环a == b 时直接返回,避免无效操作。

九、总结

这道题是并查集的经典应用,考察动态维护连通性和集合大小的能力。核心在于:

  • 熟练掌握并查集的路径压缩按秩合并优化。
  • 灵活维护额外信息(如连通块大小)。
  • 注意边界条件(如自环、重复合并)。

十、励志文案

“代码如诗,算法似剑。在青春的赛道上,我们用逻辑编织梦想,用坚持铸就辉煌。每一次AC,都是对热爱的回响!” 🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hongjianMa

感恩社区,回馈社区

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值