【Luogu3366】模板:最小生成树 §1 Prim §2 Kruskal §3 Borůvka §4 Prim分块

Link:
Luogu https://www.luogu.com.cn/problem/P3366


这波啊,这波是高强度回忆青春




Prim 算法的时间复杂度为 (O(n^2+m))
从一个结点开始扩散最小生成树,每次把树直接接着的最短边塞进来。
正确性比较显然,把图割成两半即可。

用数据结构优化一下找可行最短边,时间复杂度可以达到 (O((n+m)log n))

本来以为代码会被卡,没想到一次过了(
另外粗略感觉可以用线段树,等我睡醒上线摸余
下面是 我最喜欢写的 优先队列

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<vector>
using namespace std;
const int MAXM = 4e5 + 20;
const int MAXN = 5010;
int n, m, tot = 0;
int nxt[MAXM], head[MAXN], val[MAXM], to[MAXM];
long long Ans = 0;
bool vis[MAXN];
int mini[MAXN];
#define PII pair<int, int>
priority_queue<PII, vector<PII>, greater<PII> > Q;
#define add_edge(u, v, w) nxt[++tot]=head[u], head[u]=tot, to[tot]=v, val[tot]=w
void Update(const int& x)
{
	for (int i = head[x]; i; i = nxt[i])
	{
		if (vis[to[i]]) continue;
		if (val[i] >= mini[to[i]]) continue;
		mini[to[i]] = val[i];
		Q.push(make_pair(val[i], to[i]));
	}
}
int fre;
void Prim()
{
	fre = n - 1;
	Update(1);
	vis[1] = true;

	PII pp;
	while (Q.size())
	{
		pp = Q.top(); Q.pop();
		
		if (vis[pp.second]) continue;
		vis[pp.second] = true; --fre;
		Ans += pp.first;
		
		if (!fre) break;
		Update(pp.second);
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin >> n >> m;
	memset(mini, 0x3f, sizeof(mini));
	for (int u, v, w, i = 1; i <= m; ++i)
	{
		cin >> u >> v >> w;
		add_edge(u, v, w);
		add_edge(v, u, w);
	}
	Prim();
	if (Q.empty() && fre)
	{
		cout << "orz";
		return 0;
	}
	cout << Ans;
	return 0;
}


§2 Kruskal


Kruskal 算法的时间复杂度为 (O(mlog m))
按照边权从小到大 竹节虫 搭最小生成树
用并查集判断连一条新边之后能不能还是树

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<vector>
using namespace std;
#define rep(ii,jj,kk) for(int ii=jj;ii<=kk;++ii)
const int MAXN = 5005;
const int MAXM = 2e5 + 10;
int n, m, tot = 0;
int fa[MAXN], siz[MAXN];
long long Ans = 0;
int findfa(int x)
{
	return fa[x] == x ? x : (fa[x] = findfa(fa[x]));
}
bool merge(int x, int y)
{
	static int fx, fy;
	fx = findfa(x);
	fy = findfa(y);
	if (fx == fy) return false;
	if(siz[x] < siz[y]) fa[fx] = fy, siz[fy] += siz[fx];
	else fa[fy] = fx, siz[fx] += siz[fy];
	return true;
}
struct Edge
{
	int ptf/*from*/, ptt/*to*/, ptv/*value*/;
	Edge(int aug1 = 0, int aug2 = 0, int aug3 = 0)
	{
		ptf = aug1;
		ptt = aug2;
		ptv = aug3;
	}
}qf;
priority_queue<Edge, vector<Edge>, greater<Edge> >Q;
bool operator > (const Edge &a, const Edge &b)
{
	return a.ptv > b.ptv;
}
int main()
{
	scanf("%d%d", &n, &m);
	rep(i, 1, n) siz[i] = 1, fa[i] = i;
 	for(int u, v, w, i = 1; i <= m; ++i)
	{
		scanf("%d%d%d", &u, &v, &w);
		Q.push(Edge(u, v, w));
	}
	while(!Q.empty())
	{
	    qf = Q.top(); Q.pop();
	    if(merge(qf.ptf, qf.ptt)) Ans += qf.ptv;
	}
        if(siz[findfa(1)] == n)
        {
        	printf("%lld", Ans);
        }
        else puts("orz");
        return 0;
}


§3 Borůvka


哇哦.jpg
没啥太大的实用价值

云了一下,就是一开始每个点自成一个连通块。
每次把每个连通块往外找一条最短的边伸出去,就会让两个连通块被连起来,用并查集合并。
直到只剩下一个连通块。
这样的“每次”是 (O(log n))
复杂度是 (O((n+m)log n))


§4 Prim分块


(O(nsqrt n+m))
分块不愧是唯一神。

没必要专门分成连通块,直接按编号分块也可
其他的自己yy就能写出来啦。