[学习笔记] MST(最小生成树) - 图论
[学习笔记] MST(最小生成树) - 图论
MST,最小生成树,一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。——百度百科
对于最小生成树,有几个比较常见的性质:
- 对于任意最小生成树,它包含所有的n个节点以及n-1条边。
- 若边权都不相等的话,则它是唯一的。(由此可求第k小生成树)
- 最小生成树里不存在环。
对于找到最小生成树,有几个常见的算法:
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(最小生成树) - 图论的更多相关文章
- 【学习笔记】Tarjan 图论算法
- 前言 本文主要介绍 Tarjan 算法的「强连通分量」「割点」「桥」等算法. 争取写的好懂一些. - 「强连通分量」 - 何为「强连通分量」 在有向图中,如果任意两个点都能通过直接或间接的路径相互 ...
- Day 4 学习笔记 各种图论
Day 4 学习笔记 各种图论 图是什么???? 不是我上传的图床上的那些垃圾解释... 一.图: 1.定义 由顶点和边组成的集合叫做图. 2.分类: 边如果是有向边,就是有向图:否则,就是无向图. ...
- 图论学习笔记·$Floyd$ $Warshall$
对于图论--虽然本蒟蒻也才入门--于是有了这篇学习笔记\(qwq\) 一般我们对于最短路的处理,本蒟蒻之前都是通过构建二维数组的方式然后对每两个点进行1次深度或者广度优先搜索,即一共进行\(n\)^2 ...
- 图论 竞赛图(tournament)学习笔记
竞赛图(tournament)学习笔记 现在只是知道几个简单的性质... 竞赛图也叫有向完全图. 其实就是无向完全图的边有了方向. 有一个很有趣的性质就是:一个tournament要么没有环,如果 ...
- OI知识点|NOIP考点|省选考点|教程与学习笔记合集
点亮技能树行动-- 本篇blog按照分类将网上写的OI知识点归纳了一下,然后会附上蒟蒻我的学习笔记或者是我认为写的不错的专题博客qwqwqwq(好吧,其实已经咕咕咕了...) 基础算法 贪心 枚举 分 ...
- BZOJ 2038: [2009国家集训队]小Z的袜子(hose)【莫队算法裸题&&学习笔记】
2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec Memory Limit: 259 MBSubmit: 9894 Solved: 4561[Subm ...
- [转帖][Bash Shell] Shell学习笔记
[Bash Shell] Shell学习笔记 http://www.cnblogs.com/maybe2030/p/5022595.html 阅读目录 编译型语言 解释型语言 5.1 作为可执行程序 ...
- 莫队学习笔记(未完成QAQ
似乎之前讲评vjudge上的这题的时候提到过?但是并没有落实(...我发现我还有好多好多没落实?vjudge上的题目还没搞,然后之前考试的题目也都还没总结?天哪我哭了QAQ 然后这三道题我都是通过一道 ...
- python 学习笔记 13 -- 经常使用的时间模块之time
Python 没有包括相应日期和时间的内置类型.只是提供了3个相应的模块,能够採用多种表示管理日期和时间值: * time 模块由底层C库提供与时间相关的函数.它包括一些函数用于获取时钟时间和处 ...
- 【学习笔记】Kruskal 重构树
1. 例题引入:BZOJ3551 用一道例题引入:BZOJ3551 题目大意:有 \(N\) 座山峰,每座山峰有他的高度 \(h_i\).有些山峰之间有双向道路相连,共 \(M\) 条路径,每条路径有 ...
随机推荐
- Linux Redis 服务设置开机自启动
@ 目录 前言 一.准备工作 二.操作步骤 2.1 修改redis.conf文件 2.2 创建启动脚本 2.3 设置redis 脚本权限 2.4 设置开机启动 2.5 验证 总结 前言 请各大网友尊重 ...
- NAT类型检测方案
一.NAT分类 NAT大致有4种类型: 1. Full Cone NAT 完全锥形NAT,所有从同一个内网IP和端口号发送过来的请求都会被映射成同一个外网IP和端口号,并且任何一个外网主机都可以通过这 ...
- LaravelLumen 分组求和问题 where groupBy sum
在Laravel中使用分组求和,如果直接使用Laravel各数据库操作方法,应该会得出来如下代码式: DB::table('table_a') ->where('a','=',1) ->g ...
- 一文了解Spring Boot启动类SpringApplication
本文分享自华为云社区<[Spring Boot 源码学习]初识 SpringApplication>,作者: Huazie. 引言 往期的博文,Huazie 围绕 Spring Boot ...
- wireshark常用过滤指令
前言 wireshark是一款高效且免费的网络封包分析软件,现就自己使用过的过滤表达式进行记录,随时更新. 正文 与.或.非指令 与:and && 示例:tcp and ip.src ...
- Java常见问题-多线程
现在有 T1.T2.T3 三个线程,你怎样保证 T2 在 T1 执行完后执行,T3 在 T2 执行完后执行? 这个多线程问题比较简单,可以用 join 方法实现. 在 Java 中 Lock 接口比 ...
- Vscode控制台乱码的最终解决方案
Vscode控制台乱码的最终解决方案 vscode运行项目时控制台打印日志乱码.网上也有许多解决办法. 方法一[管用]推荐,避免过多设置 Java项目时,像Springboot微服务项目默认使用的是l ...
- CPU的保护模式
保护模式是为了克服实模式低劣的内存管理方式,物理内存地址不能直接被程序访问,程序内部的地址需要被转化为物理地址后再去访问.实模式CPU运行环境16位,保护模式32位. 寄存器扩展: 由于CPU发展到3 ...
- PN转Modbus RTU模块连接ACS4QQ变频器通信
一台完整的机器在出厂前由许多部件组成.但是,由于各种原因,这些组件来自不同的制造商,导致设备之间的通信协议存在差异.Modbus和Profinet代表两种不同的通信协议,Profinet通常用于较新的 ...
- 字符—字符与整数的关系&&常用的库函数_C
// Code file created by C Code Develop #include "ccd.h" #include "stdio.h" #incl ...