最小生成树
题目链接
【模板】最小生成树
题目描述
如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz。
输入格式
第一行包含两个整数 \(N,M\),表示该图共有 \(N\) 个结点和 \(M\) 条无向边。
接下来 \(M\) 行每行包含三个整数 \(X_i,Y_i,Z_i\) ,表示有一条长度为 \(Z_i\) 的无向边连接结点 \(X_i,Y_i\)。
输出格式
如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz。
输入
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
输出
7
说明/提示
数据规模:
对于 \(20\%\) 的数据,\(N\le 5,M\le 20\)。
对于 \(40\%\) 的数据,\(N\le 50,M\le 2500\)。
对于 \(70\%\) 的数据,\(N\le 500,M\le 10^4\)。
对于 \(100\%\) 的数据:\(\le N\le 5000,1\le M\le 2\times 10^5 ,1\le Z_i \le 10^4\)。
prim
- 时间复杂度:\(O(n^2)\)
代码
#include<bits/stdc++.h>
using namespace std;
const int N=5005,inf=0x3f3f3f3f;
int a[N][N];
int d[N];
bool v[N];
int n,m;
int prim()
{
memset(d,0x3f,sizeof d);
int res=0;
d[1]=0;
for(int i=1;i<=n;i++)
{
int x=0;
for(int j=1;j<=n;j++)
if(!v[j]&&(x==0||d[j]<d[x]))x=j;
if(d[x]==inf)return inf;
v[x]=true;
res+=d[x];
for(int j=1;j<=n;j++)
d[j]=min(d[j],a[x][j]);
}
return res;
}
int main()
{
memset(a,0x3f,sizeof a);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)a[i][i]=0;
while(m--)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
a[x][y]=a[y][x]=min(a[x][y],z);
}
int res=prim();
if(res==inf)
puts("orz");
else
printf("%d",res);
return 0;
}
kruskal
- 时间复杂度:\(O(mlogm)\)
代码
#include<bits/stdc++.h>
using namespace std;
const int N=5005,M=2e5+10;
struct rec
{
int x,y,z;
bool operator<(rec &other)
{
return z<other.z;
}
}edge[M];
int fa[N];
int n,m,s,cnt;
int res;
bool v[N];
int get(int x)
{
if(x==fa[x])return x;
return fa[x]=get(fa[x]);
}
void kruskal()
{
sort(edge,edge+m);
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=0;i<m;i++)
{
int x=get(edge[i].x),y=get(edge[i].y),w=edge[i].z;
if(x==y)continue;
cnt++;
res+=w;
fa[x]=y;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].z);
v[edge[i].x]=v[edge[i].y]=true;
}
kruskal();
if(cnt==n-1)
printf("%d",res);
else
puts("orz");
return 0;
}
Borůvka
相当于多源 \(prim\) 算法,即多个连通块同时扩展连向其他连通块的最小边,由于是所有连通块同时进行的,所以每次连通块的合并会大概有一半的连通块减少,即整个连通块的合并操作有 \(O(logn)\) 次,同时注意可能会有两个连通块选择了同一条边,需要标记防止重边,同时还可能出现环的情况,合并时判断是否在一个连通块内即可
- 时间复杂度:\(O(mlogn)\)
代码
// Problem: P3366 【模板】最小生成树
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3366
// Memory Limit: 128 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
#include <bits/stdc++.h>
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=5005,M=2e5+5;
int n,m,fa[N],chosen[N];
bool used[M];
struct
{
int x,y,w;
}a[M];
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void boruvka()
{
bool update=true;
int res=0,merged=0;
while(update)
{
update=false;
for(int i=1;i<=n;i++)chosen[i]=0;
for(int i=1;i<=m;i++)
{
if(used[i])continue;
int x=a[i].x,y=a[i].y,w=a[i].w;
x=find(x),y=find(y);
if(x==y)continue;
if(chosen[x]==0||a[chosen[x]].w>w)chosen[x]=i;
if(chosen[y]==0||a[chosen[y]].w>w)chosen[y]=i;
}
for(int i=1;i<=n;i++)
{
if(!chosen[i])continue;
int x=find(a[chosen[i]].x),y=find(a[chosen[i]].y);
if(used[chosen[i]]||x==y)continue;
used[chosen[i]]=true;
update=true;
merged++;
fa[x]=y;
res+=a[chosen[i]].w;
}
}
if(merged>=n-1)printf("%d\n",res);
else
puts("orz");
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].w);
for(int i=1;i<=n;i++)fa[i]=i;
boruvka();
return 0;
}