Link:
Luogu https://www.luogu.com.cn/problem/P3366
这波啊,这波是高强度回忆青春
§1 Prim
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就能写出来啦。