【模板】最小生成树 题解

题目难度

洛谷评级:黄

总体评级:模板

题目链接

洛谷

题目

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

\(1\le N\le 5000\),\(1\le M\le 2\times 10^5\),\(1\le Z_i \le 10^4\),\(1\le X_i,Y_i\le N\)。

前言

快要打 \(CSP-S\) 了,闲着无聊打打模板,结果 \(kruskal\) 竟一遍过了,欣喜之余发篇题解。

正文

不连通情况很简单,跑一边 \(dfs\) 如果经过的点小于 \(n\),就输出 orz。

否则,就设计了那个算法——最小生成树。

有两种算法:\(kruskal\)、\(prim\)。

本题解讲的是 \(kruskal\)。

1. 最小生成树定义

给定一个连通无向带权图

\[G=(V,E,w),\quad |V|=n,\ |E|=m \]

其中 \(w(e)\) 表示边 \(e\) 的权值。若边集 \(T\subseteq E\) 满足:

  1. \(T\) 连通;
  2. \(|T|=n-1\);

则 \(T\) 是图 \(G\) 的一棵生成树。若还满足

\[w(T)=\sum_{e\in T}w(e) \]

在所有生成树中最小,则称 \(T\) 为图 \(G\) 的最小生成树(\(Minimum Spanning Tree\),简称 \(MST\))。

最小生成树的边数一定为 \(n-1\)。最小生成树可能不唯一,但最小权值和唯一。


2. 重要性质

割性质

对于图的一个割 \((S,V\setminus S)\),若边 \(e\) 是横跨该割的所有边中权值最小的一条,则 \(e\) 一定属于某一棵最小生成树。若最小生成树唯一,则 \(e\) 必属于最小生成树。

环性质

对于图中的任意一个环,环上权值最大的边一定不属于某一棵最小生成树。若最小生成树唯一,则环上最大权边必不属于最小生成树。

\(kruskal\) 算法就是基于这两个性质:按边权从小到大考虑,能加就加(本质上是贪心(图论...贪心...\(dij\) ?(bushi))。


3. \(kruskal\) 算法

\(kruskal\) 算法流程:

  1. 将所有边按边权从小到大排序;
  2. 初始化并查集,每个点自成一个连通块;
  3. 依次枚举每条边 \((u,v,w)\):
    • 若 \(u,v\) 不在同一连通块,则加入这条边,并合并两个连通块;
    • 若 \(u,v\) 已在同一连通块,则加入会形成环,跳过;
  4. 当加入 \(n-1\) 条边时,得到最小生成树。

正确性直觉:排序后当前边 \(e\) 是尚未处理边中权值最小的。如果 \(u,v\) 不连通,则存在一个割使得 \(e\) 是横跨该割的最小边,由割性质可以加入;如果 \(u,v\) 已连通,则加入 \(e\) 会成环,且 \(e\) 是环上最大边,由环性质不应加入。


4. 并查集

并查集维护连通块。

在此不细讲并查集算法。


5. 连通性判断

最小生成树只对连通图存在。若图不连通,则不存在生成树。

本题代码先建无向邻接表,从点 \(1\) 开始 \(dfs\),统计能访问到的点数 \(cnt\)。若 \(cnt<n\),说明图不连通,输出 orz 并结束。只有连通时才继续跑 \(kruskal\)。


6. 复杂度

  • 排序:\(O(m\log m)\);
  • 并查集:近似 \(O(m\alpha(n))\),其中 \(\alpha\) 是反阿克曼函数;
  • DFS 判连通:\(O(n+m)\);
  • 总时间复杂度:\(O(m\log m)\);
  • 空间复杂度:\(O(n+m)\)。

讲完啦。

代码奉上。

#include<bits/stdc++.h>
#define ll long long
#define rep(a,b,c) for(ll (a)=(b);(a)<=(c);(a)=-~(a))
#define wep(a,b,c) for(ll (a)=(b);(a)>=(c);(a)=~-(a))
#define tep(a,b) for(auto (a):b)
using namespace std;
const ll mod=1e9+7;
const ll N=1e6+10;
ll jie[N],jie_ni[N];
namespace io{
	inline ll read() {
		ll t=0,f=1;
		char ch=getchar();
		while(ch<'0'||ch>'9'){
			if(ch=='-') f=-f;
			ch=getchar();
		}
		while(ch>='0'&&ch<='9') {
			t=t*10+ch-'0';
			ch=getchar();
		}
		return t*f;
	}
	inline void write(ll x) {
		if(x<0) {putchar('-');x=-x;}
		if(x>=10) {write(x/10);}
		putchar(char(x%10+'0'));
	}
}
namespace maths{
	ll quick(ll a,ll b){
		ll sum=1;
		while(b){
			if(b&1){
				sum*=a;
				sum%=mod;
			}
			a*=a;
			a%=mod;
			b>>=1;
		}
		return sum;
	}
	void init(){
		jie[0]=1;
		jie_ni[0]=1;
		rep(i,1,N-2){
			jie[i]=jie[i-1]*i%mod;
		}
		jie_ni[N-2]=quick(jie[N-2],mod-2);
		wep(i,N-3,1){
			jie_ni[i]=jie_ni[i+1]*(i+1)%mod;
		}
	}
	ll C(ll n,ll m){
		if(n<m||n<0||m<0){
			return 0;
		}
		return (jie[n]*jie_ni[m]%mod*jie_ni[n-m]%mod)%mod;
	}
}
using namespace io;
using namespace maths;
struct xzy{
	ll a,b,c;
	bool operator<(const xzy &g) const{
		return c<g.c;
	}
}c[N];
ll cnt=0;
ll fa[N];
ll get(ll x){
	if(fa[x]==x)
		return x;
	return fa[x]=get(fa[x]);
}
vector<ll> g[N];
bool vis[N];
void dfs(ll x){
	vis[x]=1;
	cnt++;
	tep(y,g[x]){
		if(!vis[y]){
			dfs(y);
		}
	}
}
int main(){
//    freopen(".in","r",stdin);
//    freopen(".out","w",stdout);
	ll n=read(),m=read();
	rep(i,1,n){
		fa[i]=i;
	}
	rep(i,1,m){
		ll x=read(),y=read(),z=read();
		g[x].push_back(y);
		g[y].push_back(x);
		c[i]={x,y,z};
	}
	dfs(1);
	if(cnt<n){
		puts("orz");
		return 0;
	}
	sort(c+1,c+1+m);
	ll ans=0;
	rep(i,1,m){
		ll tx=c[i].a,ty=c[i].b;
		ll fx=get(fa[tx]),fy=get(fa[ty]);
		if(fx!=fy){
			fa[fx]=fy;
			ans+=c[i].c;
		}
	}
	write(ans);
	return 0;
}
posted @ 2026-10-29 21:41  limingzhe_2030  阅读(12)  评论(0)    收藏  举报