ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

2021年SWPUACM暑假集训day3最小生成树算法

2021-07-08 00:00:46  阅读:166  来源: 互联网

标签:int day3 最小 算法 SWPUACM 2021 https 生成 com


什么是最小生成树

在讲最小生成树之前,我们先回顾一下什么是生成树:对于无向图G和一棵树T来说,如果T是G的子图,则称T为G的树,如果T是G的生成子图,则称T是G的生成树。而最小生成树就是对于一个有权值的图来说最小权值和的图就是最小生成树(也就是边权和最小的连通图,且只有n-1条边,其实也就是一颗树)

Kruskal算法

该算法的基本思想是从小到大加入边,是个贪心算法。Kruskal 算法是一种常见并且好写的最小生成树算法,由 Kruskal 发明。

原理虽然简单,但是需要一种数据结构维护一个森林,不能使其成环,或者说是维护多个集合,然后每次合并两个元素或者集合,这很容易和昨天学习的并查集联系起来,我们可以用并查集很轻松的维护这个森林。

如果使用 $ O(mlog_2m)O(mlog⁡_2m) $ 的排序算法,并且使用 $ O(mα(m,n))O(mα(m,n)) $或 O ( m l o g 2 n ) O ( m l o g 2 ⁡ n ) O(mlog_2n)O(mlog_2⁡n) O(mlog2​n)O(mlog2​⁡n) 的并查集,就可以得到时间复杂度为$ O(mlog_2m)O(mlog⁡_2m) $的 Kruskal 算法。

证明

为了造出一棵最小生成树,我们从最小边权的边开始,按边权从小到大依次加入,如果某次加边产生了环,就扔掉这条边,直到加入了$ n−1 $条边,即形成了一棵树。

证明:使用归纳法,证明任何时候 K 算法选择的边集都被某棵 MST 所包含。

基础:对于算法刚开始时,显然成立(最小生成树存在)。

归纳:假设某时刻成立,当前边集为 F,令 T 为这棵 MST,考虑下一条加入的边 e。

如果 e e e 属于 T T T,那么成立。

否则,$ T+e $一定存在一个环,考虑这个环上不属于 F F F 的另一条边 f f f(一定只有一条)。

首先,$f 的 权 值 一 定 不 会 比 的权值一定不会比 的权值一定不会比 e 小 , 不 然 小,不然 小,不然 f $会在 $e $之前被选取。

然后,$f 的 权 值 一 定 不 会 比 e 大 , 不 然 的权值一定不会比 e 大,不然 的权值一定不会比e大,不然 T+e−f 就 是 一 棵 比 就是一棵比 就是一棵比 T $还优的生成树了。

所以,$T+e−f 包 含 了 包含了 包含了 F$,并且也是一棵最小生成树,归纳成立。

代码实现

(以hdu1863为例)

#include<bits/stdc++.h>
using namespace std;

const int N = 1e2+10;
int fa[N];
int m,n;
struct edge {
    int u,v,w;
    bool operator < (const edge & a) const {
        return this->w < a.w;
    }
};
vector<edge> V;

void init() {
    for(int i = 1;i <= m; ++i) {
        fa[i] = i;
    }
    V.clear();
}

int find(int x) {
    while(x != fa[x]) x= fa[x];
    return x;
}

void kruskal() {
    int ans = 0;
    int cnt = m;
    for(int i = 0;i < V.size(); ++i) {
        int u = V[i].u;
        int v = V[i].v;
        int w = V[i].w;
        u = find(u);
        v = find(v);
        if(u != v) {
            fa[v] = u;
            ans += w;
            cnt--;
        }
    }
    if(cnt == 1) printf("%d\n",ans);
    else printf("?\n");
}

int main() 
{
    while(~scanf("%d%d",&n,&m) && n) {
        init();
        for(int i = 1;i <= n; ++i) {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            V.push_back({u,v,w});
        }
        sort(V.begin(),V.end());
        kruskal();
    }
    return 0;
}

Prim 算法

Prim 算法是另一种常见并且好写的最小生成树算法。该算法的基本思想是从一个结点开始,不断加点(而不是 Kruskal 算法的加边)。具体来说,每次要选择距离最小的一个结点,以及用新的边更新其他结点的距离。

其实跟 Dijkstra 算法一样,每次找到距离最小的一个点,可以暴力找也可以用堆维护。

堆优化的方式类似 Dijkstra 的堆优化,但如果使用二叉堆等不支持 O(1) decrease-key 的堆,复杂度就不优于 Kruskal,常数也比 Kruskal 大。所以,一般情况下都使用 Kruskal 算法,在稠密图尤其是完全图上,暴力 Prim 的复杂度比 Kruskal 优,但 不一定 实际跑得更快。所以一般不用prim算法

关于各种优化时间复杂度

暴力: O ( n 2 + m ) O(n^2+m) O(n2+m)。

二叉堆: O ( ( n + m ) l o g 2 n ) O((n+m)log_2n) O((n+m)log2​n)。

Fib 堆: O ( n l o g ⁡ 2 n + m ) O(nlog⁡_2n+m) O(nlog⁡2​n+m)。

证明

从任意一个结点开始,将结点分成两类:已加入的,未加入的。

每次从未加入的结点中,找一个与已加入的结点之间边权最小值最小的结点。

然后将这个结点加入,并连上那条边权最小的边。

重复 n−1 次即可。

证明:还是说明在每一步,都存在一棵最小生成树包含已选边集。

基础:只有一个结点的时候,显然成立。

归纳:如果某一步成立,当前边集为 F,属于 T 这棵 MST,接下来要加入边 e。

如果 e 属于 T,那么成立。

否则考虑 T+e 中环上另一条可以加入当前边集的边 f。

首先,f 的权值一定不小于 e 的权值,否则就会选择 f 而不是 e 了。

然后,f 的权值一定不大于 e 的权值,否则 T+e−f 就是一棵更小的生成树了。

因此,e 和 f 的权值相等,T+e−f 也是一棵最小生成树,且包含了 F。

代码实现

(以hdu1863为例)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
#define P pair<int, int>
#define INF 0x3f3f3f3f

const int N = 1005;
int mp[N][N];
bool vis[N];
int dis[N];
int n,m;
int prim(int s) {
	for(int i = 1; i <=n ; ++i) {
		dis[i] = INF;
		vis[i] = false;
	}
	dis[s] = 0;
	int ans = 0;
	while(true) {
		int v = -1;
		for(int u = 1; u <= n; ++u) {
			if(!vis[u] && (v == -1 || dis[u] < dis[v])) {
				v = u;
			}
		}
		if(v == -1)
		break;
		vis[v] = true;
		ans += dis[v];
		for(int u = 1; u <= n; ++u) {
			dis[u] = min(dis[u], mp[u][v]);
		}
	}
	return ans; 
}

int main()
{
	int u,v,cost;
	while(~scanf("%d%d",&m, &n) && m) {
		memset(mp,INF,sizeof mp);
		for(int i = 0; i < m; ++i) {
			scanf("%d%d%d",&u, &v, &cost);
			if(mp[u][v] > cost)
			mp[u][v] = mp[v][u] = cost;
		}
		int ans = prim(1);
		bool is = true;
		if(ans < INF) {
			printf("%d\n",ans);
		} else {
			puts("?");
		}
	}
	return 0;
}

练习题目

题目连接题目名
https://acm.dingbacode.com/showproblem.php?pid=1863畅通工程
https://acm.dingbacode.com/showproblem.php?pid=1875畅通工程再续
https://acm.dingbacode.com/showproblem.php?pid=1879继续畅通工程
https://www.luogu.com.cn/problem/P3366P3366 【模板】最小生成树
https://www.luogu.com.cn/problem/P2872P2872 [USACO07DEC]Building Roads S
https://www.luogu.com.cn/problem/P1195P1195 口袋的天空
https://www.luogu.com.cn/problem/P1194P1194 买礼物
https://www.luogu.com.cn/problem/P2121P2121 拆地毯
https://www.luogu.com.cn/problem/P1396P1396 营救
https://www.luogu.com.cn/problem/P1991P1991 无线通讯网
https://www.luogu.com.cn/problem/P4047P4047 [JSOI2010]部落划分
https://acm.dingbacode.com/showproblem.php?pid=1598find the most comfortable road

人均完成8题及以上吧

视频连接:https://www.bilibili.com/video/BV1wV411s7Pe

标签:int,day3,最小,算法,SWPUACM,2021,https,生成,com
来源: https://blog.csdn.net/m0_46201544/article/details/118560004

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有