洛谷 P14077:[GESP202509 七级] 连通图 ← DFS | 并查集

【题目来源】
https://www.luogu.com.cn/problem/P14077

【题目描述】
给定一张包含 n 个结点与 m 条边的无向图,结点依次以 1,2,…,n 编号,第 i 条边(1≤i≤m)连接结点 ui 与结点 vi。如果从一个结点经过若干条边可以到达另一个结点,则称这两个结点是连通的。
你需要向图中加入若干条边,使得图中任意两个结点都是连通的。请你求出最少需要加入的边的条数。注意给出的图中可能包含重边与自环。

【输入格式】
第一行,两个正整数 n,m,表示图的点数与边数。
接下来 m 行,每行两个正整数 ui,vi,表示图中一条连接结点 ui 与结点 vi 的边。

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

【输入样例一】
4 4
1 2
2 3
3 1
1 4

【输出样例一】
0

【输入样例二】
6 4
1 2
2 3
3 1
6 5​​​​​​​

【输出样例二】
2

【数据范围】
对于 40% 的测试点,保证 1≤n≤100,1≤m≤100。
对于所有测试点,保证 1≤n≤10^5,1≤m≤10^5。

【算法分析】
● 代码一的核心代码解析
如果一个图中有 k 个极大连通子图,可以将这 k 个极大连通子图视为 k 个整体,要使这 k 个极大连通子图成为一个连通图,最少要增加 k−1 条边。
(1)DFS 函数

void dfs(int u) {
    if(st[u]) return;
    st[u]=true;
    for(auto j:g[u]) dfs(j);
}

本文这个 DFS 函数的作用是:从给定节点 u 出发,遍历它所在的整个连通块,并将该连通块中的所有节点都标记为 “已访问”。

(2)连通块数量统计

int ans=0;
for(int i=1; i<=n; i++) {  // 遍历所有节点(1~n,注意节点编号从1开始)
    if(!st[i]) {  // 如果节点i未被访问过,说明它属于一个新的未被统计的连通块
        ans++;    // 连通块数量加1
        dfs(i);   // 调用DFS遍历这个新连通块,标记块内所有节点为已访问
    }
}
cout<<ans-1<<endl;  // 输出连通块数量-1

● 代码二的核心代码解析
代码二采用了基础并查集(https://blog.csdn.net/hnjzsyjyj/article/details/120120591)。
并查集是一种精巧且实用的数据结构,常用于处理不相交集合的合并问题。经典应用有Kruskal算法、LCA、连通子图等。

【算法代码一:DFS

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

const int N=1e5+5;
vector<int> g[N];
bool st[N];
int n,m;

void dfs(int u) {
    if(st[u]) return;
    st[u]=true;
    for(auto j:g[u]) dfs(j);
}

int main() {
    cin>>n>>m;
    while(m--) {
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    int ans=0;
    for(int i=1; i<=n; i++) {
        if(!st[i]) ans++,dfs(i);
    }
    cout<<ans-1<<endl;

    return 0;
}

/*
in:
6 4
1 2
2 3
3 1
6 5

out:
2
*/

【算法代码二:并查集

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

const int maxn=1e5+5;
int pre[maxn];

int find(int x) {
    if(x!=pre[x]) pre[x]=find(pre[x]);
    return pre[x];
}

void merge(int x,int y) {
    int a=find(x);
    int b=find(y);
    if(a!=b) pre[a]=b;
}

int main() {
    int n,m,u,v,cnt;
    cin>>n>>m;
    for(int i=1; i<=n; i++) pre[i]=i;
    for(int i=1; i<=m; i++) {
        cin>>u>>v;
        merge(u,v);
    }

    for(int i=1; i<=n; i++) {
        if(pre[i]==i) cnt++;
    }
    cout<<cnt-1<<endl;

    return 0;
}

/*
in:
6 4
1 2
2 3
3 1
6 5

out:
2
*/




【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/126455868
https://blog.csdn.net/hnjzsyjyj/article/details/120120591
https://blog.csdn.net/hnjzsyjyj/article/details/157384648
https://www.luogu.com.cn/problem/solution/P14077
https://blog.csdn.net/hnjzsyjyj/article/details/146948171


 

posted @ 2026-01-27 22:58  Triwa  阅读(8)  评论(0)    收藏  举报