题解:洛谷 P14077 连通图

【题目来源】

洛谷:P14077 [GESP202509 七级] 连通图 - 洛谷

【题目描述】

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

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

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

【输入】

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

接下来 \(m\) 行,每行两个正整数 \(u_i,v_i\),表示图中一条连接结点 \(u_i\) 与结点 \(v_i\) 的边。

【输出】

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

【输入样例】

4 4
1 2
2 3
3 1
1 4

【输出样例】

0

【核心思想】

  1. 问题分析:给定一张包含 \(n\) 个结点和 \(m\) 条边的无向图(可能含重边与自环),要求通过添加最少的边,使得图中任意两个结点都连通。本质是求原图的连通分量个数,若连通分量个数为 \(k\),则最少需要添加 \(k-1\) 条边将它们连成一棵树。

  2. 算法选择

    • 并查集(Union-Find):动态维护结点的连通关系,支持高效的合并与查询。
    • 路径压缩:在查找根节点时压缩路径,使后续查询接近 \(O(1)\)
    • 连通分量统计:遍历所有结点,统计不同根节点的数量,即为连通分量个数。
  3. 关键步骤

    • 初始化:将每个结点的父节点设为自身。
    • 合并边:对于每条边 \((u, v)\),执行 union(u, v),即 p[find(u)] = find(v),将两个端点所在集合合并。重边和自环不影响合并结果。
    • 统计连通分量:遍历 \(i = 1 \dots n\),找到每个结点的根节点,用标记数组记录出现过的根,统计不同根的个数 cnt
    • 输出答案cnt - 1,即需要添加的最少边数。
  4. 时间/空间复杂度

    • 时间复杂度:\(O(m \alpha(n))\),其中 \(\alpha(n)\) 为反阿克曼函数,近似常数,总效率极高。
    • 空间复杂度:\(O(n)\),用于存储并查集父节点数组和标记数组。
  5. 并查集的核心思想

    • 动态连通性:并查集能快速判断两个元素是否属于同一集合,并高效合并集合。
    • 树形结构表示集合:每个集合用一棵树表示,根节点作为集合代表。
    • 路径压缩优化:在查找过程中将路径上的结点直接指向根,降低树高。
    • 连通分量计数:最终不同根的数量即为连通分量数,连接所有分量成一棵生成树需要 \(\text{分量数} - 1\) 条边。
    • 适用于无向图连通性判定、Kruskal 最小生成树、网络连通等场景。

【算法标签】

普及- #并查集

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 100005;  // 定义最大节点数

int n;                  // 节点数量
int m;                  // 边数量
int ans = 0;            // 存储连通分量数量
int p[N];               // 并查集父节点数组
bool vis[N];            // 标记数组,记录是否已统计过该连通分量

/**
 * 并查集查找函数(带路径压缩)
 * @param x 要查找的节点
 * @return 节点x的根节点
 */
int find(int x)
{
    if (p[x] != x)  // 如果当前节点不是根节点
    {
        p[x] = find(p[x]);  // 路径压缩
    }
    return p[x];
}

int main()
{
    // 输入节点数和边数
    cin >> n >> m;

    // 初始化并查集,每个节点的父节点指向自己
    for (int i = 1; i <= n; i++)
    {
        p[i] = i;
    }

    // 处理每条边,合并连通分量
    while (m--)
    {
        int a, b;
        cin >> a >> b;
        // 合并a和b所在的连通分量
        p[find(a)] = find(b);
    }

    // 统计连通分量数量
    for (int i = 1; i <= n; i++)
    {
        int x = find(i);  // 找到节点i的根节点
        if (!vis[x])      // 如果该连通分量未被统计过
        {
            ans++;        // 连通分量数量加1
            vis[x] = 1;   // 标记该连通分量已统计
        }
    }

    /**
     * 输出结果:
     * 连通分量数量减1,即需要添加的边数
     * 因为要将所有连通分量连接成一棵树需要ans-1条边
     */
    cout << ans - 1 << endl;

    return 0;
}

【运行结果】

4 4
1 2
2 3
3 1
1 4
0
posted @ 2026-09-13 18:26  团爸讲算法  阅读(12)  评论(0)    收藏  举报