介绍:
最小生成树是个啥?其实就像杨志一行人押送生辰纲。抛开最后生辰纲被抢的结局不谈,杨志他们需要到好几个地方,每个地方都需要花点过路费给梁山好汉们打点。比如下面就是一张城市地图:
其中每两个图之间的路径长就是要给梁山好汉们打点的银子数。比如1号地点到2号地点的梁山好汉需要2两银子。那么问题来了,怎样才能选择其中一部分道路就可以到达所有地点且让总花费值最小呢???
如果有n个节点(城市),那就至少要连(n-1)条边(路线),并且肯定没有回路。(不信?画几个图试试)众所周知,在无向图中,只要这个图里没有回路(有(n-1)条边),那它就无疑是棵树了。(还是不信?画图......)所以,**我们就管这棵各边权值(费用)和叫“最小生成树”**了。
那这个最小生成树到底怎么搞呢?我们伟大的先哲发明了两种算法:一种叫Kruskal算法,另一种叫Prim算法。我们这里就来介绍一下名字字典序靠前的那个Kruskal吧!(我才不会告诉你根本原因是本蒟蒻不会用Prim呢!)
那咱就开始吧!首先,我们读入的数据是这样的!
1 2 2
1 3 2
1 4 4
2 3 3
3 4 4
其中每一行的a,b,c 3个数是指a到b的路径权值是c哦!
既然题目中让我们求“最小”生成树,那咱们就自然而然地想到先把这些边排个序,先用最小的边,从小到大一路把这棵树搞出来!
说干就干,排一个呗!
1 2 2
1 3 2
2 3 3
1 4 4
3 4 4
排好之后,映入我们眼帘的是“1,2,2”这条边,二话不说,把它放到生成树里!
接下来是“1,2,2”,很好,把它也加进去
然后是“2,3,3”,好多同学把它都加了进去......
等会等会,先别忙着加!好像有什么猫腻!前面说过,最小生成树,是不能带回路的,加进去一不是最小,二它连个树都不是,所以不能加!于是我们意识到:在每次加进去新边之前,一定要看看它和其它边会不会构成回路才行!
那咱就只能退而求其次,选其它的边啦,“1,4,4”这可不是回路,没问题!
至此,边数已达(n-1)条,所有点已经选上,最小生成树大功告成!(当然,选下面的“3,4,4”也是阔以的)
总结一下,我们刚才是怎样搞出这棵最小生成树的:
- 把边们从小到大排个序
- 如果没有回路,一条一条往里加
- 边数达到(n-1)条,完成
步骤都弄懂啦!但是这个“回路”到底怎么判断呢?
所谓判断回路,就是看看有了这条边之后,有没有连通的部分,从“树”的角度来看,就是两个点的根节点(也就是“祖宗”)是不是一样的
哎?话说说到这里,诸位有没有联想到什么啊?
没错,就是金光闪闪的并!查!集!
并查集是啥就不在我的责任范围之内啦,(其实就是懒癌犯了)这里我就默认泥萌都会并查集了......
所以说我们可以搞个并查集出来,然后每加入条边时,先用并查集的“查询”功能,看看两个节点的祖宗是不是一样的,如果不一样就把他俩“合并”,就可以放心大胆的往里加啦~
P3366 【模板】最小生成树
题目描述
如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz
。
输入格式
第一行包含两个整数 N,M,表示该图共有 N 个结点和 M 条无向边。
接下来 M 行每行包含三个整数 Xi,Yi,Zi,表示有一条长度为 Zi 的无向边连接结点 Xi,Yi。
输出格式
如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz
。
#include<bits/stdc++.h>
#define int long long
#define PII pair<int,int >
#define fi first
#define se second
#define pb push_back
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
const int N = 1e6+10;
int n,m;
int acc[N];
pair<int,pair<int,int>> va[N];
int find(int x)
{
if(acc[x]!=x) acc[x] = find(acc[x]);
return acc[x];
}
void krukal()
{
int cnt = 0,sum = 0;
for(int i = 1;i<=n;i++) acc[i] = i;
for(int i = 1;i<=m;i++)
{
int a = va[i].se.fi,b = va[i].se.se,c = va[i].fi;
int t1 = find(a),t2 = find(b);
if(t1!=t2)
{
acc[t1] = t2;
sum += c;
cnt++;
}
}
if(cnt==n-1) cout<<sum;
else cout<<"orz";
}
signed main()
{
IOS;
cin>>n>>m;
for(int i = 1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
va[i] = {c,{a,b}};
}
sort(va+1,va+1+m);
krukal();
return 0;
}
P2330 [SCOI2005] 繁忙的都市
题目描述
城市 C 是一个非常繁忙的大都市,城市中的道路十分的拥挤,于是市长决定对其中的道路进行改造。城市 C 的道路是这样分布的:城市中有 nn 个交叉路口,有些交叉路口之间有道路相连,两个交叉路口之间最多有一条道路相连接。这些道路是双向的,且把所有的交叉路口直接或间接的连接起来了。每条道路都有一个分值,分值越小表示这个道路越繁忙,越需要进行改造。但是市政府的资金有限,市长希望进行改造的道路越少越好,于是他提出下面的要求:
- 改造的那些道路能够把所有的交叉路口直接或间接的连通起来。
- 在满足要求 1 的情况下,改造的道路尽量少。
- 在满足要求 1、2 的情况下,改造的那些道路中分值最大的道路分值尽量小。
任务:作为市规划局的你,应当作出最佳的决策,选择哪些道路应当被修建。
输入格式
第一行有两个整数 n,m 表示城市有 n 个交叉路口,m 条道路。
接下来 m 行是对每条道路的描述,u,v,c 表示交叉路口 u 和 v 之间有道路相连,分值为 c。
输出格式
两个整数 s,max,表示你选出了几条道路,分值最大的那条道路的分值是多少。
#include<bits/stdc++.h>
#define int long long
#define PII pair<int,int >
#define fi first
#define se second
#define pb push_back
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
const int N = 1e6+10;
int n,m;
int acc[N];
pair<int,pair<int,int>> va[N];
int find(int x)
{
if(acc[x]!=x) acc[x] = find(acc[x]);
return acc[x];
}
void krukal()
{
int cnt = 0,sum = 0;
for(int i = 1;i<=n;i++) acc[i] = i;
for(int i = 1;i<=m;i++)
{
int a = va[i].se.fi,b = va[i].se.se,c = va[i].fi;
int t1 = find(a),t2 = find(b);
if(t1!=t2)
{
acc[t1] = t2;
cnt++;
}
if(cnt==n-1)
{
cout<<cnt<<" "<<c;
break;
}
}
}
signed main()
{
IOS;
cin>>n>>m;
for(int i = 1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
va[i] = {c,{a,b}};
}
sort(va+1,va+1+m);
krukal();
return 0;
}
P1195 口袋的天空
题目背景
小杉坐在教室里,透过口袋一样的窗户看口袋一样的天空。
有很多云飘在那里,看起来很漂亮,小杉想摘下那样美的几朵云,做成棉花糖。
题目描述
给你云朵的个数 N,再给你 M 个关系,表示哪些云朵可以连在一起。
现在小杉要把所有云朵连成 K 个棉花糖,一个棉花糖最少要用掉一朵云,小杉想知道他怎么连,花费的代价最小。
输入格式
第一行有三个数 N,M,K。
接下来 M 行每行三个数 X,Y,L,表示 X 云和 Y 云可以通过 L 的代价连在一起。
输出格式
对每组数据输出一行,仅有一个整数,表示最小的代价。
如果怎么连都连不出 K 个棉花糖,请输出 No Answer
。
题解:
首先让我们选k朵云做棉花糖,换句话说就是选k个节点构造最小生成树,那不好办!我们都知道,全部n个点的最小生成树有(n-1)条边,那选k个节点就是(n-k)条边呗!
其次就是这个“No Answer”怎么弄的问题,其实也so easy,你想啊,一共就m条边,如果都选完了还没有选出(n-k)条边来,那就是妥妥的No Answer 了
说了这么多,是时候放代码了!听我这么一讲解,大家应该都理解最小生成树的Kruskal算法了吧!
#include<bits/stdc++.h>
#define int long long
#define PII pair<int,int >
#define fi first
#define se second
#define pb push_back
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
const int N = 1e5+10;
int n,m,k;
int acc[N];
pair<int,pair<int,int>> va[N];
int find(int x)
{
if(acc[x]!=x) acc[x] = find(acc[x]);
return acc[x];
}
void krukal()
{
int cnt = 0,sum = 0;
for(int i = 1;i<=n;i++) acc[i] = i;
for(int i = 1;i<=m;i++)
{
int a = va[i].se.fi,b = va[i].se.se,c = va[i].fi;
int t1 = find(a),t2 = find(b);
if(t1!=t2)
{
acc[t1] = t2;
cnt++;
sum += c;
}
if(cnt==n-k) break;
}
if(cnt==n-k) cout<<sum;
else cout<<"No Answer";
}
signed main()
{
IOS;
cin>>n>>m>>k;
for(int i = 1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
va[i] = {c,{a,b}};
}
sort(va+1,va+1+m);
krukal();
return 0;
}
P1547 [USACO05MAR] Out of Hay S
题目描述
Bessie 计划调查 N(2≤N≤2 000)个农场的干草情况,它从 1 号农场出发。农场之间总共有 M(1≤M≤104)条双向道路,所有道路的总长度不超过 109。有些农场之间存在着多条道路,所有的农场之间都是连通的。
Bessie 希望计算出该图中最小生成树中的最长边的长度。
输入格式
第一行两个整数 N,M。
接下来 M 行,每行三个用空格隔开的整数 Ai,Bi,Li,表示 Ai,Bi 之间有一条道路,长度为 Li。
输出格式
一个整数,表示最小生成树中的最长边的长度。
#include<bits/stdc++.h>
#define int long long
#define PII pair<int,int >
#define fi first
#define se second
#define pb push_back
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
const int N = 1e6+10;
int n,m;
int acc[N];
pair<int,pair<int,int>> va[N];
int find(int x)
{
if(acc[x]!=x) acc[x] = find(acc[x]);
return acc[x];
}
void krukal()
{
int cnt = 0,sum = 0;
for(int i = 1;i<=n;i++) acc[i] = i;
for(int i = 1;i<=m;i++)
{
int a = va[i].se.fi,b = va[i].se.se,c = va[i].fi;
int t1 = find(a),t2 = find(b);
if(t1!=t2)
{
acc[t1] = t2;
cnt++;
sum += c;
}
if(cnt==n-1)
{
cout<<c;
break;
}
}
}
signed main()
{
IOS;
cin>>n>>m;
for(int i = 1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
va[i] = {c,{a,b}};
}
sort(va+1,va+1+m);
krukal();
return 0;
}
P2872 [USACO07DEC] Building Roads S
题目描述
给定 n 个点的坐标,第 i 个点的坐标为 (xi,yi),这 n 个点编号为 1 到 n。给定 m 条边,第 i 条边连接第 ui 个点和第 vi 个点。现在要求你添加一些边,并且能使得任意一点都可以连通其他所有点。求添加的边的总长度的最小值。
输入格式
第一行两个整数 n,m 代表点数与边数。
接下来 n 行每行两个整数 xi,yi 代表第 i 个点的坐标。
接下来 m 行每行两个整数 ui,vi 代表第 i 条边连接第 ui 个点和第 vi 个点。
输出格式
一行一个实数代表添加的边的最小长度,要求保留两位小数,为了避免误差, 请用 64 位实型变量进行计算。
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define pb push_back
#define fi first
#define int long long
#define pii pair<int,int >
#define se second
using namespace std;
const int N=2e6+10;
int acc[N];
int n,m;
struct node
{
int u,v;
double w;
}va[N];
struct Node
{
int x,y;
}vb[N];
bool cmp(node a,node b)
{
if(a.w!=b.w) return a.w<b.w;
}
int find(int x)
{
if(acc[x]!=x) acc[x]=find(acc[x]);
return acc[x];
}
signed main()
{
IOS;
cin>>n>>m;
int sum=0;
double sum1 = 0.0;
for(int i=1;i<=n;i++)
{
cin>>vb[i].x>>vb[i].y;
}
for(int i=1;i<=n;i++) acc[i]=i;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
sum++;
va[sum].u=i;
va[sum].v=j;
va[sum].w=sqrt(pow(vb[i].x-vb[j].x,2)+pow(vb[i].y-vb[j].y,2));
}
}
for(int i=1;i<=m;i++)
{
sum++;
int a,b;
cin>>a>>b;
va[sum].u=a;
va[sum].v=b;
va[sum].w=0.0;
}
sort(va+1,va+1+sum,cmp);
int cnt = 0;
for(int i=1;i<=n*n;i++)
{
if(find(va[i].u)!=find(va[i].v))
{
acc[find(va[i].u)]=find(va[i].v);
sum1+=va[i].w;
cnt++;
}
else continue;
if(cnt==n-1) printf("%.2lf",sum1);
}
}