【模板】最小生成树 题解
题目难度
洛谷评级:黄
总体评级:模板
题目链接
洛谷
题目
给出一个无向图,求出最小生成树,如果该图不连通,则输出 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. 最小生成树定义
给定一个连通无向带权图
其中 \(w(e)\) 表示边 \(e\) 的权值。若边集 \(T\subseteq E\) 满足:
- \(T\) 连通;
- \(|T|=n-1\);
则 \(T\) 是图 \(G\) 的一棵生成树。若还满足
在所有生成树中最小,则称 \(T\) 为图 \(G\) 的最小生成树(\(Minimum Spanning Tree\),简称 \(MST\))。
最小生成树的边数一定为 \(n-1\)。最小生成树可能不唯一,但最小权值和唯一。
2. 重要性质
割性质
对于图的一个割 \((S,V\setminus S)\),若边 \(e\) 是横跨该割的所有边中权值最小的一条,则 \(e\) 一定属于某一棵最小生成树。若最小生成树唯一,则 \(e\) 必属于最小生成树。
环性质
对于图中的任意一个环,环上权值最大的边一定不属于某一棵最小生成树。若最小生成树唯一,则环上最大权边必不属于最小生成树。
\(kruskal\) 算法就是基于这两个性质:按边权从小到大考虑,能加就加(本质上是贪心(图论...贪心...\(dij\) ?(bushi))。
3. \(kruskal\) 算法
\(kruskal\) 算法流程:
- 将所有边按边权从小到大排序;
- 初始化并查集,每个点自成一个连通块;
- 依次枚举每条边 \((u,v,w)\):
- 若 \(u,v\) 不在同一连通块,则加入这条边,并合并两个连通块;
- 若 \(u,v\) 已在同一连通块,则加入会形成环,跳过;
- 当加入 \(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;
}

浙公网安备 33010602011771号