好像一直没怎么见到蓝桥杯考到最小生成树hhh
说不定今年就考了
本章内容
- 并查集
- 基于并查集的Kruskal算法
- 类似Dijkstra的Prim算法
1、并查集
在给出最小生成树模板之前,要先掌握并查集这个东西。
并查集是什么呢?
我个人理解是:维护两个查询的点是否存在相同的性质(是不是属于同一个集合)
并查集首先需要一个祖先数组p[n]
,来存每个节点的祖先是谁
其次,查询操作使用的是find(int x)
函数
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
在将a和b的祖先 pa和pb加入同一个集合时,只需要将p[pa] = pb
即可 ,再使用一次find函数查询一次即可
2、基于并查集的Kruskal算法
思想很简单,将 边 按权重大小排序,从排好序的边中选择n - 1条即可
(选择的边的两个端点不能已经连通了(并查集))
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
int n, m;
int p[N]; //并查集
struct Edge
{
int a, b, w;
bool operator < (const Edge &W)const //重载小于号
{
return w < W.w;
}
}edges[N];
int find(int x) //找祖先的函数
{
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m;
for(int i = 0; i < m; i ++)
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[i].a = a, edges[i].b = b, edges[i].w = w;
}
sort(edges, edges + m);
//并查集初始化
for(int i = 1; i <= n; i ++) p[i] = i;
int res = 0, cnt = 0;
for(int i = 0; i < m; i ++)
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
int pa = find(a), pb = find(b);
if(pa != pb)
{
p[pb] = pa;
res += w;
cnt ++;
}
}
if(cnt < n - 1) puts("impossible"); //n个节点的图,生成树有n - 1条边
else cout << res << endl;
return 0;
}
3、类似Dijkstra的Prim算法
Prim的思想其实是按 点 扩展的
每次更新的是点到集合的距离,每次只选取最近的未加入集合的点进行扩展
#include<bits/stdc++.h>
using namespace std;
const int N = 510;
int n, m;
int g[N][N];
bool st[N];
int dist[N];
int prim()
{
int distance = 0;
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 1; i <= n; i ++)
{
int t = -1;
for (int j = 1; j <= n; j ++)
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
st[t] = true;
distance += dist[t];
for (int j = 1; j <= n; j ++)
dist[j] = min(dist[j], g[t][j]);
}
if (abs(distance) > 5000000) return 5000000;
else return distance;
}
int main()
{
cin >> n >> m;
memset(g, 0x3f, sizeof g);
while (m -- )
{
int a, b, c;
cin >> a >> b >> c;
g[a][b] = g[b][a] = min(g[a][b], c);
}
int res = prim();
if (res == 5000000) puts("impossible");
else cout << res << endl;
return 0;
}
泻药,当年蓝桥杯拿了省一,算是还愿了😄 感谢y总 现在在fdu读研~