洛谷P3366【模板】最小生成树-克鲁斯卡尔Kruskal算法详解附赠习题
题目描述
如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz
输入输出格式
输入格式:
第一行包含两个整数N、M,表示该图共有N个结点和M条无向边。(N<=5000,M<=200000)
接下来M行每行包含三个整数Xi、Yi、Zi,表示有一条长度为Zi的无向边连接结点Xi、Yi
输出格式:
输出包含一个数,即最小生成树的各边的长度之和;如果该图不连通则输出orz
输入输出样例
输入样例#1:4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3输出样例#1:7说明
时空限制:1000ms,128M
数据规模:
对于20%的数据:N<=5,M<=20
对于40%的数据:N<=50,M<=2500
对于70%的数据:N<=500,M<=10000
对于100%的数据:N<=5000,M<=200000
分析
这是一道求最小生成树的模板题。
假设一个拥有n个节点的图,若它是个连通图,则可求其最小生成树。什么是最小生成树?先来解释一下生成树。
如果连通图 G的一个子图是一棵包含G 的所有顶点的树,则该子图称为G的生成树。
简而言之,就是通过连接一些边而使这个图的任意节点之间直接或间接地相连通。最小生成树,可以理解为连接的边的边权和最小时的生成树。一般而言,常用的求最小生成树的方法有克鲁斯卡尔Kruskal和普里姆Prim算法,在本篇中仅介绍克鲁斯卡尔算法。它的思想其实非常好理解,我们把每个点看做一个集合。
- 在一开始,设每个点各成一个集合,也就是运用并查集的思想。
- 读入边后,将边从小到大排序,运用的是贪心思想。这样可以保证最终的结果值最小。
- 我们从最小的边开始,如果这条边两端的点不在一个集合内,就把这条边连起来,再把两端的点并成一个集合。
- 当连接的边的数量是点数-1时,我们就得到我们要求的最小生成树了。
”当连接的边的数量是点数-1时“,为什么呢?若这个图中有n个点,则我们要做的是把n个集合连成一个集合,边数当然必须是n-1。多一条则会成环,少一条则无法联通。
以输入样例为例,这个图一开始就是这样的(样例似乎有些不合理,画图水平也实在不行,将就着看):
我们对边进行排序,得到最小的边是连接1,2和1,3的两条边。这里我们先处理1,2这条边,我们知道它们此刻当然不在一个集合中,于是连接。
如上图,sum表示当前所求出的边权之和。这时该操作的就是1,3之间的连线,它们目前并不属于一个集合,所以我们把它们连起来,点1,2,3就处于一个集合中啦。
再接下来剩下三条边,权值分别为3,3,4,那么我们先处理1,4之间的这条边,很明显点4现在并不与1处于同一个集合。我们把它连起来。
这时我们发现,连接的边数为3,已经等于点数-1了,退出操作,输出结果7.观察此时的图,四个点确实都在连线中,这个图的最小生成树值为7.
这就是克鲁斯卡尔Kruskal的总体思想,非常易于理解。
代码
首先考虑如何存边,由上面的思考过程我们可以想到,我们希望可以快捷简便的获得一条边的序号、起止点和权值。二维数组显然不是理想解法,我们考虑结构体。
struct bia{
int s,t;//s表示起点,t表示终点
long long c;//c表示权值,使用long long或int应依数据范围决定
};
bia b[];
如果要将结构体表示的边从小到大进行排序,直接使用sort会出问题。我们可以自己编写一个cmp函数,表示排序规则。
bool cmp(bia a,bia b){
if(a.c<b.c)
return ;
return ;
}
我个人习惯一种更简洁的写法,一会会在完整程序中展现。
考虑到”把每个点看成一个集合“的并查集思想,我们当然也需要设一个数组fa[]表示每个点所属集合啦。但在这里我们只需要并查集比较常见的查询,合并可以直接用简洁的fa[u]=v来表示。
int find(int x){
if(fa[x]==x)
return x;
else
{
fa[x]=find(fa[x]);
return fa[x];
}
}
这里同样有一种更简洁的写法。
处理完这些,我们就可以编写克鲁斯卡尔算法的主体程序了。
void kru(){
for(int i=;i<=m;i++)//按权值从小到大的顺序对边进行操作
{
int u=find(b[i].s),v=find(b[i].t);//u和v分别表示这条边两端节点所属的集合
if(u==v)
continue;//若该边连接的两点已经处于一个集合中,则不必进行连接,直接进行下一条边
fa[u]=v;//将该边连接的两点进行合并
ans+=b[i].c;//ans即上图中的sum,表示所求的最小生成树之值
s++;//用s统计目前已连接的边数,若已连接n-1条边,则结束程序,输出结果
if(s==n-)
return;
}
}
这样我们就完成了克鲁斯卡尔算法,建议先通过洛谷P3366【模板】最小生成树这道题。AC代码如下。
#include <iostream>
#include <algorithm>//sort需要的头文件
using namespace std;
struct bia{
int s,t;//s表示起点,t表示终点
long long c;//c表示权值,使用long long或int应依数据范围决定
};
bool cmp(bia a,bia b){
return a.c<b.c;//这是一种更简洁的写法
}
bia b[];//用b数组存储边
int n,m,ans,s,fa[];//n为点数,m为边数,ans为答案,s为已连接边数,fa[]表示点所在的集合
int find(int x){
return x==fa[x]?x:fa[x]=find(fa[x]);//也是一种简便写法
}
void kru(){
for(int i=;i<=m;i++)//从最小的边开始操作
{
int u=find(b[i].s),v=find(b[i].t);//u和v分别表示该条边两端节点所处的集合
if(u==v)
continue;//若两点属于同一集合,该边不需要连接,直接操作下一条
fa[u]=v;//将两点并入同一集合
ans+=b[i].c;//将该边边权加入答案
s++;//累计目前已连接边的边数,若为n-1则结束运算并输出答案
if(s==n-)
return;
}
}
int main()
{
cin>>n>>m;
for(int i=;i<=n;i++)
fa[i]=i;//初始化每个点各成一个集合
for(int i=;i<=m;i++)
cin>>b[i].s>>b[i].t>>b[i].c;
sort(b+,b+m+,cmp);
kru();
if(s==n-)
cout<<ans;
else
cout<<"orz";//实际上本题并不需要输出orz,数据中没有无解情况,但我们仍应考虑所有题目条件
return ;
}
评测结果(截图是无注释版的结果,仍可以进行快读快写之类的优化,但这个题显然并不需要)
附
值得一提的是,克鲁斯卡尔算法的时间复杂度为O(mlogn),普里姆算法的复杂度为O(n^2),优化后可达到O(mlogn)(实际上还可进行优化,但编程复杂度也会相应提高许多)。在面对稀疏图(边数较少的图)时不妨采用克鲁斯卡尔算法,在面对稠密图(边数较多)的图时则可考虑普里姆优化算法。
完成模板题后,不如再进行几道练习题练手——都是较为简单的题目,对模板稍加改动即可。附上AC代码,复制粘贴可能被kkk制裁哦。
#include <iostream>
#include <algorithm>
using namespace std;
struct bia{
int s,t;
long long c;
};
bool cmp(bia a,bia b){
return a.c<b.c;
}
bia a[];
int n,k,sum,ans,s,fa[];
int find(int x){
return x==fa[x]?x:find(fa[x]);
}
void kru(){
for(int i=;i<=k;i++)
{
int u=find(a[i].s),v=find(a[i].t);
if(u==v)
continue;
fa[u]=v;
s++;
sum+=a[i].c;
if(s==n-)
return;
}
}
int main()
{
cin>>n>>k;
for(int i=;i<=n;i++)
fa[i]=i;
for(int i=;i<=k;i++)
cin>>a[i].s>>a[i].t>>a[i].c,ans+=a[i].c;
sort(a+,a+k+,cmp);
kru();
ans-=sum;
cout<<ans;
return ;
}
局域网
#include <iostream>
#include <algorithm>
using namespace std;
struct bia{
int s,t;
int c;
};
bool cmp(bia a,bia b){
return a.c<b.c;
}
bia a[];
int n,m,ans,sum,fa[];
int find(int x){
return x==fa[x]?x:find(fa[x]);
}
void kru(){
for(int i=;i<=m;i++)
{
int u=find(a[i].s),v=find(a[i].t);
if(u==v)
continue;
fa[u]=v;
sum++;
ans=max(ans,a[i].c);
if(sum==n-)
return;
}
}
int main()
{
cin>>n>>m;
for(int i=;i<=n;i++)
fa[i]=i;
for(int i=;i<=m;i++)
cin>>a[i].s>>a[i].t>>a[i].c;
sort(a+,a+m+,cmp);
kru();
cout<<sum<<" "<<ans;
return ;
}
繁忙的都市
#include <iostream>
#include <algorithm>
using namespace std;
struct bia{
int s,t,c,y;
};
bool cmp(bia a,bia b){
if(a.y!=b.y)
return a.y<b.y;
else
return a.c<b.c;
}
bia a[];
int n,m,sum,ans,fa[],j;
int find(int x){
return x==fa[x]?x:find(fa[x]);
}
void kru(){
for(int i=;i<=m;i++)
{
int u=find(a[i].s),v=find(a[i].t);
if(a[i].y==)
{
if(u==v)
continue;
}
else
{
if(u==v)
sum--;
}
fa[u]=v;
sum++;
ans+=a[i].c;
if(sum==n-)
{
j=i;
return;
}
}
}
int main()
{
cin>>n>>m;
for(int i=;i<=n;i++)
fa[i]=i;
for(int i=;i<=m;i++)
cin>>a[i].y>>a[i].s>>a[i].t>>a[i].c;
sort(a+,a+m+,cmp);
kru();
for(int i=j;i<=n;i++)
if(a[i].y==)
ans+=a[i].c;
else
break;
cout<<ans;
return ;
}
联络员
#include <iostream>
using namespace std;
int fa[],n,m,x1,y1,x2,y2,sum;
int find(int x){
return x==fa[x]?x:fa[x]=find(fa[x]);
}
bool unionn(int a,int b){
int u=find(a),v=find(b);
if(u!=v)
{
fa[v]=u;
return ;
}
else
return ;
}
int main()
{
cin>>n>>m;
for(int i=;i<=n*m;i++)
fa[i]=i;
while(cin>>x1>>y1>>x2>>y2)
unionn((x1-)*m+y1,(x2-)*m+y2);
for(int i=;i<=m;i++)
for(int j=;j<n;j++)
if(unionn((j-)*m+i,j*m+i))
sum++;
for(int i=;i<=n;i++)
for(int j=;j<m;j++)
if(unionn((i-)*m+j,(i-)*m+j+))
sum+=;
cout<<sum;
return ;
}
连接格点
洛谷P3366【模板】最小生成树-克鲁斯卡尔Kruskal算法详解附赠习题的更多相关文章
- 图解最小生成树 - 克鲁斯卡尔(Kruskal)算法
我们在前面讲过的<克里姆算法>是以某个顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树的.同样的思路,我们也可以直接就以边为目标去构建,因为权值为边上,直接找最小权值的边来构建生成树 ...
- [洛谷P3366] [模板] 最小生成树
存个模板,顺便复习一下kruskal和prim. 题目传送门 kruskal 稀疏图上表现更优. 设点数为n,边数为m. 复杂度:O(mlogm). 先对所有边按照边权排序,初始化并查集的信息. 然后 ...
- 图的生成树(森林)(克鲁斯卡尔Kruskal算法和普里姆Prim算法)、以及并查集的使用
图的连通性问题:无向图的连通分量和生成树,所有顶点均由边连接在一起,但不存在回路的图. 设图 G=(V, E) 是个连通图,当从图任一顶点出发遍历图G 时,将边集 E(G) 分成两个集合 T(G) 和 ...
- Kruskal算法详解
本章介绍克鲁斯卡尔算法.和以往一样,本文会先对克鲁斯卡尔算法的理论论知识进行介绍,然后给出C语言的实现.后续再分别给出C++和Java版本的实现. 最小生成树 在含有n个顶点的连通图中选择n-1条边, ...
- 克鲁斯卡尔(Kruskal)算法求最小生成树
/* *Kruskal算法求MST */ #include <iostream> #include <cstdio> #include <cstring> #inc ...
- 洛谷 P1967 货车运输(克鲁斯卡尔重构树)
题目描述 AAA国有nn n座城市,编号从 11 1到n nn,城市之间有 mmm 条双向道路.每一条道路对车辆都有重量限制,简称限重.现在有 qqq 辆货车在运输货物, 司机们想知道每辆车在不超过车 ...
- 克鲁斯卡尔(Kruskal)算法
# include <stdio.h> # define MAX_VERTEXES //最大顶点数 # define MAXEDGE //边集数组最大值 # define INFINITY ...
- 算法笔记_066:Kruskal算法详解(Java)
目录 1 问题描述 2 解决方案 2.1 构造最小生成树示例 2.2 伪码及时间效率分析 2.3 具体编码(最佳时间效率) 1 问题描述 何为Kruskal算法? 该算法功能:求取加权连通图的最小 ...
- 【算法设计与分析基础】24、kruskal算法详解
首先我们获取这个图 根据这个图我们可以得到对应的二维矩阵图数据 根据kruskal算法的思想,首先提取所有的边,然后把所有的边进行排序 思路就是把这些边按照从小到大的顺序组装,至于如何组装 这里用到并 ...
随机推荐
- NOIP2016提高A组五校联考2总结
第一题用组合数各种乱搞,其恶心程度不一般.搞了很久才调对,比赛上出了一点bug,只拿了30分. 第二题我乱搞得出个错误的结论,本来自信满满60分,结果爆零了. 第三题,树形dp,在一开始的时候想到了, ...
- python 字符词串和字符串的转换
type(' i am ') str type(''.join('i am')) str ''.join('sda sadaa') 'sda sadaa' q=str('i am the teache ...
- Apollo配置中心环境搭建(Linux)
官方教程:https://github.com/ctripcorp/apollo/wiki/Apollo-Quick-Start-Docker%E9%83%A8%E7%BD%B2 方式二:使用apol ...
- [SHOI2005]树的双中心
题目链接:Click here Solution: 首先我们要知道,选择两个点\(A,B\),必定存在一条边,割掉这条边,两个集合分别归\(A,B\)管 再结合题目,我们就得到了一个暴力的\(n^2\ ...
- 【BZOJ2118】墨墨的等式(同余最短路)
题意: 思路:From https://www.cnblogs.com/GavinZheng/p/11709153.html#4421510 写的1e9,int范围的 #include<bits ...
- FP AUTO节点ZPP002M执行卡住解决
正常情况下,不到一分钟即可执行完ZPP002M节点 异常情况下,超过十分钟都没有响应 再等待只会影响FP的执行时间,影响后续的节点,解决办法是将正在执行的JOB STOP掉 再到服务器上将该节点重新执 ...
- Unity PlayerPrefs 存储的位置
Mac OS 在Mac OS X上PlayerPrefs是存储在~/Library/Preferences文件夹,名为unity.[company name].[product name].plist ...
- ACM ICPC 2011-2012 Northeastern European Regional Contest(NEERC)B Binary Encoding
B: 现在有一种新的2进制表示法,要你求出0~m-1的每个数的表示. 规则如下:n 是满足 m<=2n 最小数. 而0~m-1的数只能够用n-1个位和n个位来表示. 对于n个位表示的数来说不能有 ...
- 无法加载程序集XXX.dll 此程序集可能是从 Web 上下载的
错误 13 无法加载程序集 file:///D:\Documents\Downloads\kaxaml-master\kaxaml-master\packages\Prism.4.0.0. ...
- RCU原理分析
简介 RCU(Read-Copy Update)是数据同步的一种方式,在当前的Linux内核中发挥着重要的作用.RCU主要针对的数据对象是链表,目的是提高遍历读取数据的效率,为了达到目的使用RCU机制 ...