[学习笔记] MST(最小生成树) - 图论

MST,最小生成树,一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。——百度百科

对于最小生成树,有几个比较常见的性质:

  1. 对于任意最小生成树,它包含所有的n个节点以及n-1条边。
  2. 若边权都不相等的话,则它是唯一的。(由此可求第k小生成树)
  3. 最小生成树里不存在环。

对于找到最小生成树,有几个常见的算法:

kruskal

图片来源于oi wiki(下同)

主要运用贪心思想,按边权的大小排序,时间复杂度\(O(m\, logm)\),适合用于稀疏图,通常跑的非常快,代码也较为简单。

//洛谷P3366版子 下同
#include<bits/stdc++.h>
using namespace std;
#define s(n) scanf("%d", &n)
int n, m, f[5010];
struct Edge{ int u, v, w; }e[200100];
bool cmp(Edge a, Edge b){
return a.w < b.w;
}
inline int ff(int k){
if(f[k] == k) return k;
return f[k] = ff(f[k]);
}
inline void kruskal(){
sort(e + 1, e + m + 1, cmp);
for(int i=1; i<=n; i++) f[i] = i;
int ans = 0, cnt = 0;
for(int i=1; i<=m; i++){
if(cnt == n - 1) break; //小小的优化,可要可不要
int fa = ff(e[i].u), fb = ff(e[i].v);
if(fa == fb) continue;
else{
ans += e[i].w;
cnt++;
f[fa] = fb;
}
}
if(cnt == n - 1) printf("%d", ans);
else printf("orz");
return;
} int main(){
s(n), s(m);
for(int i=1; i<=m; i++) s(e[i].u), s(e[i].v), s(e[i].w);
kruskal();
return 0;
}

prim

prim算法是由Dijkstra发明的,并且与dijkstra最短路算法发表在同一篇论文中。prim算法与dijksrta非常像,复杂度\(O(m\,longn)\)。适合稠密图。但它跑的不一定有kruskal快。

#include<bits/stdc++.h>
using namespace std;
#define s(n) scanf("%d", &n)
int n, m, x, y, z;
struct edge{
int to, w;
edge(int a, int b){
to = a, w = b;
}
};
vector<edge> G[200001];
struct node{
int id, dis;
node(int a, int b){
id = a, dis = b;
}
bool operator < (const node &u) const{
return dis > u.dis;
}
};
bool done[5005];
inline void prim(){
int s = 1;
for(int i=1; i<=5005; i++) done[i] = 0;
priority_queue<node> q;
q.push(node(s, 0));
int ans = 0, cnt = 0;
while(!q.empty()){
node u = q.top(); q.pop();
if(done[u.id]) continue;
done[u.id] = 1;
ans += u.dis;
cnt++;
for(int i=0; i<G[u.id].size(); i++){
edge y = G[u.id][i];
if(done[y.to]) continue;
q.push(node(y.to, y.w));
}
}
if(cnt == n) printf("%d", ans);
else printf("orz");
return;
}
int main(){
s(n), s(m);
for(int i=1; i<=m; i++){
s(x), s(y), s(z);
G[x].push_back(edge(y, z));
G[y].push_back(edge(x, z));
}
prim();
return 0;
}

例题说明

它在实际运用中非常的灵活:

1.灵活选边

题目描述:

Tyvj已经一岁了,网站也由最初的几个用户增加到了上万个用户,随着Tyvj网站的逐步壮大,管理员的数目也越来越多,现在你身为Tyvj管理层的联络员,希望你找到一些通信渠道,使得管理员两两都可以联络(直接或者是间接都可以)。Tyvj是一个公益性的网站,没有过多的利润,所以你要尽可能的使费用少才可以。

目前你已经知道,Tyvj的通信渠道分为两大类,一类是必选通信渠道,无论价格多少,你都需要把所有的都选择上;还有一类是选择性的通信渠道,你可以从中挑选一些作为最终管理员联络的通信渠道。数据保证给出的通行渠道可以让所有的管理员联通。

思路

比如在联络员一题中,你需要现将必须纳入的边直接加到ans里去,然后再排序可加可不加的边,跑算法

#include<bits/stdc++.h>
using namespace std;
#define s(n) scanf("%d", &n)
int n, m, x, y, z, p, f[2010], ans=0, cnt=1;
struct edge{ int u, v, w; }e[10010];
bool cmp(edge a, edge b){ return a.w < b.w; }
inline int ff(int k){
if(f[k] != k) f[k] = ff(f[k]);
return f[k];
}
inline void kruskal(){
sort(e + 1, e + cnt, cmp);
for(int i=1; i<=cnt; i++){
int fa = ff(e[i].u), fb = ff(e[i].v);
if(fa == fb) continue;
f[fa] = fb;
ans += e[i].w;
}
printf("%d", ans);
}
int main(){
s(n), s(m);
for(int i=1; i<2010; i++) f[i] = i;
for(int i=1; i<=m; i++){
s(p), s(x), s(y), s(z);
if( p == 1){
int fa = ff(x), fb = ff(y);
f[fa] = fb;
ans += z;
}
e[cnt++] = (edge){x, y, z};
}
kruskal();
return 0;
}

2.设置虚点

题目描述

农夫约翰决定给他的N(1<=N<=300)个牧场浇水,这些牧场被自然的命名为1..N。他可以给一个牧场引入水通过在这个牧场挖一口井或者修一条管道使这个牧场和一个已经有水的牧场连接。

在牧场i挖一口井的花费是w_i(1<=w_i<=100000)。修建一条水管连接牧场i和牧场j的花费是p_ij(1<=p_ij<=100000;p_ij=p_ji;p_ii=0)。

请确定农夫约翰为了完成浇灌所有的牧场所需的最小的总花费。

思路

挖水井中,我原先以为找到最小花费的井然后以这个井为初始点跑prim就行。但我错了,因为最小井可能不唯一,而且MST也可能不唯一。所以可以设置一个虚点,把所有井都连到这个点上去,边权为挖井的花费。用这个有n+1的图跑算法就行了。

#include<bits/stdc++.h>
using namespace std;
#define s(n) scanf("%d", &n)
int n, x, minn = INT_MAX, cnt=1, f[310];
long long ans = 0;
struct edge{
int u, v, w;
}e[900001];
bool cmp(edge a, edge b){
return a.w < b.w;
}
inline int ff(int k){
if(f[k] != k) f[k] = ff(f[k]);
return f[k];
}
inline void kruskal(){
sort(e + 1, e + 1 + n*n + n, cmp);
for(int i=1; i<=n; i++) f[i] = i;
int cnt = 0;
for(int i=1; i<=n*n+n; i++){
if(cnt == n) break;
int fa = ff(e[i].u), fb = ff(e[i].v);
if(fa == fb) continue;
f[fa] = fb;
cnt++;
ans += e[i].w;
}
printf("%lld", ans);
return;
}
int main(){
s(n);
for(int i=1; i<=n; i++){
s(x);
e[cnt++] = (edge){i, n+1, x};
e[cnt++] = (edge){n+1, i, x};
}
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
s(x);
if(i == j) continue;
e[cnt++] = (edge){i, j, x};
}
}
kruskal();
return 0;
}

合理调整边权

题目描述

Farmer John变得非常懒, 他不想再继续维护供奶牛之间供通行的道路. 道路被用来连接N 个牧场, 牧场被连续地编号为1..N. 每一个牧场都是一个奶牛的家. FJ计划除去P条

道路中尽可能多的道路, 但是还要保持牧场之间的连通性. 你首先要决定那些道路是需要保留的N-1条道路. 第j条双向道路连接了牧场S_j和E_j , 而且走完它需要L_j 的时间.

没有两个牧场是被一条以上的道路所连接. 奶牛们非常伤心, 因为她们的交通系统被削减了. 你需要到每一个奶牛的住处去安慰她们. 每次你到达第i个牧场的时候(即使你已

经到过), 你必须花去C_i的时间和奶牛交谈. 你每个晚上都会在同一个牧场(这是供你选择的)过夜, 直到奶牛们都从悲伤中缓过神来. 在早上起来和晚上回去睡觉的时候, 你都

需要和在你睡觉的牧场的奶牛交谈一次. 这样你才能完成你的交谈任务. 假设Farmer John采纳了你的建议, 请计算出使所有奶牛都被安慰的最少时间。

思路

安慰奶牛这道题中,根据题干n-1条边可知要求MST。通过画个草图可以知道每个边都要跑两遍,但还要考虑每个点的权值,这怎么办呢?模拟一下,在MST上,我们走每一条边时,都要加上起始点和到达点的权值,并且加边权时并不是加一次,而是要加两次。整个过程中最后一定会回到开始选择的那个点,所以还要再加一次开始点的权值。 于是每条边的边权就变成了这个样子:\(edge\_ fine[i]\, =\, begin\, +\, end\, +\, edge[i]*2\)

然后用新权值跑算法就行了。不过在跑的时候还要找到权值最小的开始点,这很简单,对吧~

#include<bits/stdc++.h>
using namespace std;
#define s(n) scanf("%d", &n)
int n, p, x, y, z, f[10001], cnt, ans=0, node[10001], mn=INT_MAX;
struct edge{ int u, v, w; }e[100010];
bool cmp(edge a, edge b){ return a.w < b.w; }
inline int ff(int k){
if(f[k] != k) f[k] = ff(f[k]);
return f[k];
}
inline void kruskal(){
for(int i=1; i<=n; i++) f[i] = i;
sort(e + 1, e + p + 1, cmp);
cnt = 0;
for(int i=1; i<=p; i++){
if(cnt == n) continue;
int fa = ff(e[i].u), fb = ff(e[i].v);
if(fa == fb) continue;
f[fa] = fb;
cnt++;
ans += e[i].w;
mn = min(mn, min(node[e[i].u], node[e[i].v]));
}
printf("%d", ans + mn);
}
int main(){
s(n), s(p);
for(int i=1; i<=n; i++) s(node[i]);
for(int i=1; i<=p; i++){
s(x), s(y), s(z);
e[i] = (edge){x, y, z * 2 + node[x] + node[y]};
}
kruskal();
return 0;
}

小数据可以打暴力~

the end

[学习笔记] MST(最小生成树) - 图论的更多相关文章

  1. 【学习笔记】Tarjan 图论算法

    - 前言 本文主要介绍 Tarjan 算法的「强连通分量」「割点」「桥」等算法. 争取写的好懂一些. - 「强连通分量」 - 何为「强连通分量」 在有向图中,如果任意两个点都能通过直接或间接的路径相互 ...

  2. Day 4 学习笔记 各种图论

    Day 4 学习笔记 各种图论 图是什么???? 不是我上传的图床上的那些垃圾解释... 一.图: 1.定义 由顶点和边组成的集合叫做图. 2.分类: 边如果是有向边,就是有向图:否则,就是无向图. ...

  3. 图论学习笔记·$Floyd$ $Warshall$

    对于图论--虽然本蒟蒻也才入门--于是有了这篇学习笔记\(qwq\) 一般我们对于最短路的处理,本蒟蒻之前都是通过构建二维数组的方式然后对每两个点进行1次深度或者广度优先搜索,即一共进行\(n\)^2 ...

  4. 图论 竞赛图(tournament)学习笔记

    竞赛图(tournament)学习笔记 现在只是知道几个简单的性质... 竞赛图也叫有向完全图. 其实就是无向完全图的边有了方向. ​ 有一个很有趣的性质就是:一个tournament要么没有环,如果 ...

  5. OI知识点|NOIP考点|省选考点|教程与学习笔记合集

    点亮技能树行动-- 本篇blog按照分类将网上写的OI知识点归纳了一下,然后会附上蒟蒻我的学习笔记或者是我认为写的不错的专题博客qwqwqwq(好吧,其实已经咕咕咕了...) 基础算法 贪心 枚举 分 ...

  6. BZOJ 2038: [2009国家集训队]小Z的袜子(hose)【莫队算法裸题&&学习笔记】

    2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec  Memory Limit: 259 MBSubmit: 9894  Solved: 4561[Subm ...

  7. [转帖][Bash Shell] Shell学习笔记

    [Bash Shell] Shell学习笔记 http://www.cnblogs.com/maybe2030/p/5022595.html  阅读目录 编译型语言 解释型语言 5.1 作为可执行程序 ...

  8. 莫队学习笔记(未完成QAQ

    似乎之前讲评vjudge上的这题的时候提到过?但是并没有落实(...我发现我还有好多好多没落实?vjudge上的题目还没搞,然后之前考试的题目也都还没总结?天哪我哭了QAQ 然后这三道题我都是通过一道 ...

  9. python 学习笔记 13 -- 经常使用的时间模块之time

    Python 没有包括相应日期和时间的内置类型.只是提供了3个相应的模块,能够採用多种表示管理日期和时间值: *    time 模块由底层C库提供与时间相关的函数.它包括一些函数用于获取时钟时间和处 ...

  10. 【学习笔记】Kruskal 重构树

    1. 例题引入:BZOJ3551 用一道例题引入:BZOJ3551 题目大意:有 \(N\) 座山峰,每座山峰有他的高度 \(h_i\).有些山峰之间有双向道路相连,共 \(M\) 条路径,每条路径有 ...

随机推荐

  1. python 判断token是否有效,若失效,重新发起token请求

    场景: 1.对一个接口,进行接口自动化测试,查找的是有权限操作的用例,传入到获取token接口,生成token,判断当前是否有token,如果存在token,则无需再次发起token接口: 存在的问题 ...

  2. 关于Android开机动画调试的一点小小的经验

    格式要求: 开机动画图片既可以是jpg文件,也可以是png文件,只是一定要按顺序命名.文件名命名的国际惯例是五位数,即:00000.00001. 00002--也可以是文件夹名称+下划线+序数,即:p ...

  3. Codeforces Round 955 (Div. 2, with prizes from NEAR!) codeforces div2 955

    A. Soccer ------------------------题解--------------- 给你开始比分和结束比分问你中间两队比分有没有相等过有可能就是YES不可能就是NO 结束时两队比分 ...

  4. (sql语句试题练习及 参考答案解题思路+个人解题思路)

    SQL字段说明及数据 ======================================================================= 一.部门表字段描述:dp_no 部 ...

  5. C#中重写(override)及覆盖(new)的区别详解

    1. 重写和覆盖的定义 1.1 重写(override)的定义   在C#中,用override关键字来重写一个父类中的虚方法或抽象方法.override关键字用于指示编译器,我要用派生类中的一个方法 ...

  6. Top cluster 树分块

    写点基础的东西.随便写的,勿喷. top cluster 一个 cluster 是一个联通子图,且至多有两个点与其他部分连接 这两个点被称为 boundary node 其余点被称为 internal ...

  7. python3 模型日记

    说明 作为一种 python 框架模型的记录吧,用于个人总结,不定时更新. 正文 1. 主进程退出后,子进程也跟着退出 之前遇到过一种情况,用 flet 写了一个页面,然后又同时开了一个 tcp se ...

  8. SpringBoot项目启动执行任务的几种方式

    1.直接在启动类下面调用方法 @SpringBootApplication public class TestApplication { public static void main(String[ ...

  9. Spring手动获取bean的四种方式

    一.实现BeanFactoryPostProcessor接口 @Component public class SpringUtil implements BeanFactoryPostProcesso ...

  10. DASCTF X GFCTF 2024|四月开启第一局 [PWN]详解

    DASCTF X GFCTF 2024|四月开启第一局[PWN] wp(详解) 1.dynamic_but_static 题目保护情况 64位程序,没有开canary和pie保护,got表可改 64位 ...