✏️ 纠错
第 152 题 / 共 155 题

题目描述

给定一张包含 n 个结点与 m 条边的无向图,结点依次以 1,2,…,n 编号,第 i 条边(1≤i≤m)连接结点 ui​ 与结点 vi​。如果从一个结点经过若干条边可以到达另一个结点,则称这两个结点是连通的。

你需要向图中加入若干条边,使得图中任意两个结点都是连通的。请你求出最少需要加入的边的条数。

注意给出的图中可能包含重边与自环。

输入格式

第一行,两个正整数 n,m,表示图的点数与边数。

接下来 m 行,每行两个正整数 ui​,vi​,表示图中一条连接结点 ui​ 与结点 vi​ 的边。

输出格式

输出一行,一个整数,表示使得图中任意两个结点连通所需加入的边的最少数量。

输入输出样例

输入 #1

4 4
1 2
2 3
3 1
1 4

输出 #1

0

输入 #2

6 4
1 2
2 3
3 1
6 5

输出 #2

2

说明/提示

对于 40% 的测试点,保证 1≤n≤100,1≤m≤100。

对于所有测试点,保证 1≤n≤105,1≤m≤105。

📝 题目解析

问题核心:求使无向图连通所需添加的最少边数。

关键观察
一个连通图的「连通分量」(彼此独立的子图)数量为k时,最少需要k-1条边即可将所有分量连接成一个连通图(例如:3个分量需2条边)。

解决方案:使用「并查集」(Union-Find)计算连通分量数量,最终答案为分量数- 1。

代码注释

#include <algorithm>
#include <cstdio>
using namespace std;

const int N = 1e5 + 5; // 最大节点数(1e5+5 避免越界)

int n, m;        // n:节点数,m:边数
int f[N];        // 并查集的父节点数组(f[u]表示 u 的父节点)
int ans;         // 连通分量数量

// 查找 u 的根节点(路径压缩优化)
int getf(int u) {
    // 如果 f[u]为 0,说明 u 是根节点;否则递归查找父节点并压缩路径
    return f[u] ? f[u] = getf(f[u]) : u;
}

int main() {
    scanf("%d%d", &n, &m);

    // 处理每条边,合并两个节点所在的集合
    for (int i = 1; i <= m; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        // 查找 u 和 v 的根节点
        int root_u = getf(u);
        int root_v = getf(v);
        // 若根节点不同,则合并(将 root_u 的父节点设为 root_v)
        if (root_u != root_v) {
            f[root_u] = root_v;
        }
    }

    // 统计连通分量数量(根节点的数量)
    for (int i = 1; i <= n; i++) {
        // 若 i 是根节点(getf(i) == i),则分量数+1
        if (getf(i) == i) {
            ans++;
        }
    }

    // 最少需要添加的边数 = 分量数 - 1
    printf("%d\n", ans - 1);
    return 0;
}
 

复杂度分析

时间复杂度:O(n+ma(n)),其中a(n)是并查集的反阿克曼函数(近似常数,可忽略),适用于n,m≤1e5的数据范围。

空间复杂度:O(n),用于存储并查集的父节点数组。