最小生成树
最小生成树对应的图一般都是无向图,有向图需要用其它不常用的算法
P858. Prim算法求最小生成树
和dijkstra非常像
集合指当前的生成树
一个点到集合的距离: 一个点到集合的所有边中,取一个距离最小的叫做这个点到集合的距离,如果一个点没有一条边到集合,那么这个点到集合的距离为正无穷
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, m;
int g[N][N];
int dist[N];
bool st[N]; //含义同dijkstra
int prim(){
memset(dist, 0x3f, sizeof dist);
int res = 0;
for (int i = 0; i < n; i ++ ){
int t = -1;
for (int j = 1; j <= n; j++ ){
if(!st[j] && (t == -1 || dist[t] > dist[j]))
t=j;
}
if (i && dist[t] == INF) return INF;
if (i) res += dist[t];
//上面两行不能写后面,因为如果写后面当t有自环时dist[t]又会被更新
st[t]=true;
for (int j = 1; j <= n; j ++ ){
dist[j] = min(dist[j], g[t][j]); //注意这行和dijkstra的区别
}
}
return res;
}
int main(){
memset(g, 0x3f, sizeof g);
scanf("%d%d", &n, &m);
while (m -- ){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = g[b][a] = min(g[a][b], c);
}
int t = prim();
if (t == INF) puts("impossible");
else printf("%d\n", t);
return 0;
}
P859. Kruskal算法求最小生成树
比堆优化prim思路简单多了
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 100010, M = 200010, INF = 0x3f3f3f3f;
int n, m;
int p[N];
struct Edge{
int a, b, w;
}edges[M];
bool cmp(Edge a, Edge b){
return a.w < b.w;
}
int find (int x){
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int kruskal()
{
sort(edges, edges + m, cmp);
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;
a = find(a), b = find(b);
if (a != b){
p[a] = b;
res += w;
cnt ++ ;
}
}
if (cnt < n-1) return INF;
return res;
}
int main(){
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i ++ ){
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[i] = {a, b, w};
}
int t = kruskal();
if (t == INF) puts("impossible");
else printf("%d\n", t);
return 0;
}
正确性证明可用反证法:
P1140. 最短网络
模板题
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 110;
int n;
int w[N][N];
int dist[N];
bool st[N];
int prim()
{ //同上面的模板
memset(dist, 0x3f, sizeof dist);
int res = 0;
for (int i = 0; i < n; i ++ )
{
int t = -1;
for (int j = 1; j <= n; j++ )
{
if(!st[j] && (t == -1 || dist[t] > dist[j]))
t=j;
}
if (i) res += dist[t];
st[t]=true;
for (int j = 1; j <= n; j ++ )
{
dist[j] = min(dist[j], w[t][j]);
}
}
return res;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
scanf("%d", &w[i][j]);
int t = prim();
printf("%d\n", t);
return 0;
}
P1141. 局域网
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 110, M = 210;
int p[N];
int n, m;
struct Edge{
int a, b, w;
}edges[M];
bool cmp(Edge a, Edge b)
{
return a.w < b.w;
}
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int kruskal()
{
sort(edges, edges + m, cmp);
for (int i = 1; i <= n; i ++ ) p[i] = i; //初始化并查集
int res = 0;
for (int i = 0; i < m; i ++ ){
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b);
if (a != b) p[a] = b;
else res += w; //要在这里加
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i++ )
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[i] = {a, b, w};
}
int t=kruskal();
printf("%d\n", t);
return 0;
}
P1142. 繁忙的都市
求最大值的最小可以想到二分,此题确实可以用二分 + 并查集 (可以看题解),这里还是采用最小生成树
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 310, M = 8010;
int p[N];
int n, m;
struct Edge{
int a, b, w;
}edges[M];
bool cmp(Edge a, Edge b)
{
return a.w < b.w;
}
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int kruskal()
{
sort(edges, edges + m, cmp);
for (int i = 1; i <= n; i ++ ) p[i] = i; //初始化并查集
int res = 0;
for (int i = 0; i < m; i ++ ){
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b);
if (a != b)
{
p[a] = b;
res = w; //当最后一条边加入时就是答案了
}
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i++ )
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[i] = {a, b, w};
}
int t=kruskal();
printf("%d %d\n", n-1, t);
return 0;
}
P1143. 联络员
kruskal拓展
注意这题初始化并查集不能再写到函数中了,因为一开始读边时就要用并查集
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 2010, M = 10010;
int p[N];
int n, m;
int res;
int idx; //存所有非必选边
struct Edge{
int a, b, w;
}edges[M];
bool cmp(Edge a, Edge b)
{
return a.w < b.w;
}
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int kruskal()
{
sort(edges, edges + idx, cmp);
for (int i = 0; i < idx; i ++ )
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b);
if (a != b)
{
p[a] = b;
res += w;
}
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) p[i] = i; //初始化并查集
for (int i = 0; i < m; i++ )
{
int k, a, b, w;
scanf("%d%d%d%d", &k, &a, &b, &w);
if (k == 1)
{
p[find(a)] = find(b);
res += w;
}
else edges[idx++] = {a, b, w};
}
int t=kruskal();
printf("%d\n", t);
return 0;
}
P1144. 连接格点
方法基本通上题,先把连接好的边连上,再从剩下的边里面选,因为怕排序时间比较紧(kruskal算法时间瓶颈就在排序上),我们先建纵向的边(权值为1),再建横向的边(权值为2),这样就省去了一步排序的过程
排序与不排序的差距:
二维转为一维的方式可以参考:AcWing 1131. 拯救大兵瑞恩
建边函数get_edges类似bfs,每次从一个点搜上下左右四条边,但要想办法先加纵边再加横边
一开始给每个点一个专属的编号,类似下面这样:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1010, M = N * N, K = 2 * N * N;
//M为点数,K为边数
int n, m, idx;
int ids[N][N]; //二维坐标转一维
int p[M];
struct Edge{
int a, b, w;
}edges[K];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
void get_edges() //建边函数,类似bfs
{
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1}, dw[] = {1, 2, 1, 2};
for (int z = 0; z < 2; z ++ )
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++ )
for (int u = 0; u < 4; u ++ )
{
if (u % 2 == z) //保证先建纵边
{
int x = i + dx[u], y = j + dy[u], w = dw[u];
if(x >= 1 && x <= n && y >= 1 && y <= m)
{
int a = ids[i][j], b = ids[x][y];
if(a < b) edges[idx++] = {a, b, w}; //防止加两次边
}
}
}
}
int kruskal()
{
int res = 0;
for (int i = 0; i < idx; i ++ )
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b);
if (a != b)
{
p[a] = b;
res += w;
}
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
//二维坐标转一维,给每个点一个编号t
for (int i = 1, t = 1; i <= n; i ++ )
for (int j = 1; j <=m; j ++ , t ++ )
ids[i][j] = t;
for (int i = 1; i <= n * m; i ++ ) p[i] = i; //初始化并查集
int x1, y1, x2, y2;
while (cin >> x1 >> y1 >> x2 >> y2)
{
int a = ids[x1][y1], b = ids[x2][y2];
p[find(a)] = find(b);
}
get_edges();
int t = kruskal();
printf("%d\n", t);
return 0;
}