黔西南布依族苗族自治州网站建设_网站建设公司_表单提交_seo优化
2026/1/14 19:43:31 网站建设 项目流程

题目描述

如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz

输入格式

第一行包含两个整数 N,M,表示该图共有 N 个结点和 M 条无向边。

接下来 M 行每行包含三个整数 Xi​,Yi​,Zi​,表示有一条长度为 Zi​ 的无向边连接结点 Xi​,Yi​。

输出格式

如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出orz

输入输出样例

输入 #1复制运行

4 5 1 2 2 1 3 2 1 4 3 2 3 4 3 4 3

输出 #1复制运行

7

说明/提示

数据规模:

对于 20% 的数据,N≤5,M≤20。

对于 40% 的数据,N≤50,M≤2500。

对于 70% 的数据,N≤500,M≤104。

对于 100% 的数据:1≤N≤5000,1≤M≤2×105,1≤Zi​≤104,1≤Xi​,Yi​≤N。

样例解释:

所以最小生成树的总边权为 2+2+3=7。

1. 问题引入

在图论中,最小生成树是一个非常经典的问题:

在一个拥有N个顶点和M条边的带权无向图中,如何找到一棵树,使得它包含所有N个顶点,且所有边的权值之和最小?

此外,还有一个常见变种:如果图本身就是断开的(不连通),我们该如何判断?

本文将基于 Kruskal 算法,结合并查集,给出一种通解。

2. 核心算法:Kruskal (克鲁斯卡尔)

Kruskal 算法的核心思想是“贪心”

算法步骤:

  1. 排序:将所有的边按照权值(w)从小到大排序。我们希望尽可能的选权值小的边。

  2. 遍历:按顺序枚举每一条边(u, v)。

  3. 判断

    • 如果u和v已经在同一个集合里(连通),说明加上这条边会形成环,跳过

    • 如果u和v不在同一个集合,说明这条边连接了两个独立的连通分量,选中它。

    • 将u和v通过并查集合并。

  4. 终止:当我们选够了N-1条边时,最小生成树构建完成。

关键点:连通性判断

如果遍历完所有边,选中的边数仍然小于N-1,说明图是不连通的(有孤岛),此时无法生成 MST,按照题目要求输出 "orz" 。

3. 核心代码解析

3.1 结构体运算符重载

为了方便排序,我们直接在结构体内部重载<运算符。这样调用sort时就不需要写额外的cmp函数,代码封装性更好。

struct edge{ int x,y,w; //重载运算符 让边集数组按照边权从小到大排序 friend bool operator <(edge a,edge b){ return a.w<b.w; } }e[200010];//边集数组 edge mst[5010];//记录最小生成树每条边

3.2 并查集的路径压缩

这是 Kruskal 能高效运行的保证。find函数在递归查找祖先的过程中,直接将沿途节点的父指针指向根节点,将树高度压扁。

int find(int x) { if(fa[x]==x) return x; return fa[x]=find(fa[x]); // 路径压缩,下次查就是 O(1) }

4. 完整代码

//kruskal版 //首先明确什么是不连通,不连通代表图有多个连通分量 //即最后生成最小生成树后所用的边小于n-1(n是节点数) #include <iostream> #include <algorithm>//sort函数头文件 using namespace std; int fa[5010];//并查集 记录每个节点的父/祖先节点(集合中的老大) int n,m;//n个结点 m条无向边 int ans=0;//ans是mst(最小生成树)的边数 记录连了多少条边 //并查集查询+路径压缩 int find(int x){ //如果已经是根结点(集合老大)就返回 if(fa[x]==x) return x; //否则就递归找老大,并把老大给到沿途所有节点 return fa[x]=find(fa[x]); } void uni(int u,int v){ int fau=find(u);//找u老大 int fav=find(v);//找v老大 //如果老大相同就说明已经在一个集合就不需要操作 if(fau!=fav){//如果不同,就让u的老大变成v的老大的老大 fa[fav]=fau; } } struct edge{ int x,y,w; //重载运算符 让边集数组按照边权从小到大排序 friend bool operator <(edge a,edge b){ return a.w<b.w; } }e[200010];//边集数组 edge mst[5010];//记录最小生成树每条边 int main(){ ios::sync_with_stdio(false); cin.tie(0); cin>>n>>m;//n个结点 m条无向边 //初始化每个节点的老大为自己(每个点自己就是一个连通分量) for(int i=1;i<=n;i++) fa[i]=i; //创建边集数组 for(int i=1;i<=m;i++){ cin>>e[i].x>>e[i].y>>e[i].w; } //边集数组按照边权从小到大排序 sort(e+1,e+m+1); int ma=0;//记录最小生成树各边的长度之和 //取每一条边,看这条边的两端是不是同一个连通分量 for(int i=1;i<=m;i++){ int u=e[i].x; int v=e[i].y; //如果u v不连通 就把它两连起来 并把边权加进max //如果已经连通就跳过 if(find(u)!=find(v)){ ans++;//每连一条边 ans+1 //存最小生成树的边(这道题用不到) mst[ans].x=u; mst[ans].y=v; mst[ans].w=e[i].w; uni(u,v);//这条边的两端需要变成一个连通分量(集合) ma+=e[i].w; //最小生成树最多只能有n-1条边 if(ans==n-1) break; } } //如果 连接的边数==节点数-1,说明是连通图 if(ans==n-1) cout<<ma; //不等于 说明有多个连通分量 else cout<<"orz"; return 0; }

5. 总结与易错点

  1. 关于 "orz":Kruskal算法不仅能求最短路径和,还能天然地判断图的连通性。如果循环结束后ans<n-1,说明即使把所有能连的边都连上,图还是断开的。

  2. 数据范围fa数组开N大小,e数组开M大小。不要搞混。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询