最小生成树例题及解析

最小生成树——Prim算法与Kruskal算法

本章一共有六道题.

Prim算法

1.最短网络

2.城市公交网建设

3.疫情

Kruskal算法

1.局域网

2.繁忙的都市

3.联络员

Prim算法

1.最短网络

Description

Farmer John被选为他们镇的镇长!他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。当然,他需要你的帮助。Farmer John已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。为了使花费最少,他想铺设最短的光纤去连接所有的农场。你将得到一份各农场之间连接费用的列表,你必须找出能连接所有农场并所用光纤最短的方案。每两个农场间的距离不会超过 100000。

Format

Input

第一行,农场的个数,N(3 ≤ N ≤ 100)。

第二行至结尾,接下来的行包含了一个 N × N 的矩阵,表示每个农场之间的距离。理论上,他们是 N 行,每行由 N 个用空格分隔的数组成,实际上,他们每行限制在 80 个字符以内,因此,某些行会紧接着另一些行。当然,对角线将会是 0,因为线路从第 i 个农场到它本身的距离在本题中没有意义。

Output

只有一个输出,是连接到每个农场的光纤的最小长度和。

Samples

输入数据 1

4
0 4 9 21
4 0 8 17
9 8 0 16
21 17 16 0

输出数据 1

28

思路

这题很简单,输入矩阵f(i,j)就相当于输入每条边(i,j)的权值。

最后做一次最小生成树即可。

代码

#include<bits/stdc++.h>
using namespace std;
#define pa pair<int,int>
int ans,n,z,g[2010],now;
priority_queue<pa ,vector<pa >,greater<pa > >p;
bool vis[2010];
vector<pa >v[2010];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>z;
			v[i].push_back({z,j});//建图
		}
	}
	for(int i=1;i<=n;i++){
		vis[i]=0;
		g[i]=1e9;//初始化
	}
	for(int j=1;j<=n;j++){
		vis[j]=0;
		g[j]=1e9;//初始化
	}
	g[1]=0;
	p.push({0,1});//权值,位置
	while(!p.empty()){
		now=p.top().second; //当前位置
		p.pop();
		if(vis[now])continue;
		vis[now]=1;
		ans+=g[now];//加上这条边
		for(int i=0;i<v[now].size();i++){
			if(!vis[v[now][i].second]&&g[v[now][i].second]>v[now][i].first){ 
				g[v[now][i].second]=v[now][i].first;
				p.push({g[v[now][i].second],v[now][i].second});//加入队列
			}
		}
	}
	cout<<ans;
	return 0;
}

2.城市公交网建设

Description

有一张城市地图,图中的顶点为城市,无向边代表两个城市间的连通关系,边上的权为在这两个城市之间修建高速公路的造价,研究后发现,这个地图有一个特点,即任一对城市都是连通的。现在的问题是,要修建若干高速公路把所有城市联系起来,问如何设计可使得工程的总造价最少?

Format

Input

第一行,两个整数 n(城市数 1 ≤ n ≤ 100),e(边数);

以下 e 行,每行 3 个数 i,j,w,表示在城市 i,j 之间修建高速公路的造价。

Output

n - 1 行,每行为两个城市的序号,表明这两个城市间建一条高速公路。

最后一行输出工程最少造价。

Samples

输入数据 1

6 10
1 2 10
1 3 12
1 5 15
2 3 7
3 5 12
2 4 5
2 6 6
3 6 8
5 6 10
4 6 6

输出数据 1

1 2
2 3
2 4
6 5
2 6
38

思路

这题要在Prim算法的基础上改一些东西。我们要求边是哪几条,要在遍历的时候存当前这个节点与哪个点相联,若确定要使用这条边,则把这条边的两个点加入队列。值得注意的是,观察数据,我们发现输出的时候是按边的第二个点从小到大输出的。

代码

#include<bits/stdc++.h>
using namespace std;
#define pa pair<int,int>
int ans,n,m,x,y,z,g[2010],q[2010],now;
priority_queue<pa ,vector<pa >,greater<pa > >p,v2;
bool vis[2010];
vector<pa >v[2010];
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>x>>y>>z;
		v[x].push_back({z,y});
		v[y].push_back({z,x});//建图
	}
	for(int j=1;j<=n;j++){
		vis[j]=0;
		g[j]=1e9;
	}
	g[1]=0;
	p.push({0,1});
	while(!p.empty()){
		now=p.top().second; 
		p.pop();
		if(vis[now])continue;
		vis[now]=1;
		ans+=g[now];//加上权值
		if(q[now])v2.push({now,q[now]});//反着存,能让输出和题目要求的输出一样
		for(int i=0;i<v[now].size();i++){
			if(!vis[v[now][i].second]&&g[v[now][i].second]>v[now][i].first){ 
				g[v[now][i].second]=v[now][i].first;
				q[v[now][i].second]=now;//存与之相连的点
				p.push({g[v[now][i].second],v[now][i].second});
			}
		}
	}
	while(!v2.empty()){
		cout<<v2.top().second<<" "<<v2.top().first<<endl;//反回来输出
		v2.pop();
	}
	cout<<ans;
	return 0;
}

3.疫情

Description

疫情来势汹汹,一个区域内的城镇要共同抵御疫情。

这个区域内共有 N 座城镇,其中 Q 个城镇已经建好了防疫站,为了防疫工作的需要,每个城镇必须有公路通到至少一个防疫站,现在已有一些修好的路可以利用。修建第 i 个城镇到第 j 个城镇的公路花费 cost(i,j),还有有 P 个城镇由于条件优越,可以花钱建防疫站。这 N 个城镇的人民找到了会编程的你,至少要花多少钱可以完成防疫?

Format

Input

第 1 行,3 个整数 N,Q,P。

接下来 Q 行,每行 1 个整数 Ki​,表示第 Ki 个城镇已有防疫站;

接下来 P 行,每行 2 个整数 Ti 和 pricei,表示在第 Ti 个城镇修建防疫站的价格为 pricei;

接下来 N×N 的矩阵,第 i 行第 j 列的整数表示 cost(i,j);

最后 N×N 的矩阵,第 i 行第 j 列的整数表示第 i 个城镇与第 j 个城镇之间的道路有无情况,为 0 或者 1,1 表示已经修建好的,0 表示还没有道路。

题目保证两个矩阵都是对称的(a[i][j]=a[j][i])且主对角线都为0(a[i,i]=0,i=1...n)。

Output

1 行。1 个整数 ans,表示最少的花费。

输入数据 1

3 1 1
1
3 1
0 3 3
3 0 5
3 5 0
0 1 0
1 0 0
0 0 0

输出数据 1

1

Explanation

样例一说明:

城镇 1 已建好防疫站,且 1−2 有道路已经修好。只需在城镇 3 建防疫站,花费 1。

Limitation

对于 20% 的数据,P=0;Q=1;

对于 30% 的数据,N≤10;

对于 100% 的数据,N≤700;0≤Q,P≤N;cost,priceN≤700;cost,price 均为 1−100000 间的整数;题目保证 P+Q>0。

思路

刚开始就有的防疫站,任何地方到那都不用花费。而可以修的防疫站,任何地方到那都是到那修路/修防疫站的最小值。若刚开始没有现成的防疫站,则使用贪心,建一个最便宜的防疫站,再从这个点开始做最小生成树。如果有,那么随便从一个有防疫站的地方开始做最小生成树就可以了。

代码

#include<bits/stdc++.h>
using namespace std;
#define pa pair<int,int>
int ans,n,Q,P,z,g[2010],k[2010],t[2010],pr[2010],a[2010][2010],now,s,minn;
priority_queue<pa ,vector<pa >,greater<pa > >p;
bool vis[2010];
vector<pa >v[2010];
int main(){
	cin>>n>>Q>>P;
	for(int i=1;i<=Q;i++)cin>>k[i];
	for(int i=1;i<=P;i++){
		cin>>t[i]>>pr[i];//输入 
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>a[i][j];//输入 
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>z;
			if(z==1)a[i][j]=0;//有了,不用花费 
		}
	}
	for(int i=1;i<=Q;i++){
		for(int j=1;j<=n;j++){
			a[j][k[i]]=0;//不用花费
		}
	}
	for(int i=1;i<=P;i++){
		for(int j=1;j<=n;j++){
			a[j][t[i]]=min(a[j][t[i]],pr[i]);//修路/修站
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			v[i].push_back({a[i][j],j});//建图
		}
	}
	for(int i=1;i<=n;i++){
		vis[i]=0;
		g[i]=1e9;//初始化 
	}
	if(Q==0){
		for(int i=1;i<=P;i++){
			if(pr[i]<minn){
				minn=pr[i];
				s=i;//本来没要现成的站,建站最小值(贪心) 
			}
		}
		g[s]=minn;//防疫站的最小花费
		p.push({minn,s});//从这个点开始做,加上本身的权值
	}else{
		g[k[1]]=0;
		p.push({0,k[1]});//有现成的 
	}
	while(!p.empty()){
		now=p.top().second; 
		p.pop();
		if(vis[now])continue;
		vis[now]=1;
		ans+=g[now];
		for(int i=0;i<v[now].size();i++){
			if(!vis[v[now][i].second]&&g[v[now][i].second]>v[now][i].first){ 
				g[v[now][i].second]=v[now][i].first;
				p.push({g[v[now][i].second],v[now][i].second});
			}
		}
	}//Prim
	cout<<ans;
	return 0;
}

Kruskal算法

1.局域网

Description

某个局域网内有 n(n ≤ 100)台计算机,由于搭建局域网时工作人员的疏忽,现在局域网内的连接形成了回路,我们知道如果局域网形成回路那么数据将不停的在回路内传输,造成网络卡的现象。

因为连接计算机的网线本身不同,所以有一些连线不是很畅通,我们用 f(i,j)表示 i,j 之间连接的畅通程度(f(i,j)≤ 1000),f(i,j)值越小表示 i,j 之间连接越通畅,f(i,j)为 0 表示 i,j 之间无网线连接。现在我们需要解决回路问题,我们将除去一些连线,使得网络中没有回路,并且被除去网线的 Σ f(i,j)最大,请求出这个最大值。

Format

Input

第一行两个正整数 n,k;

接下来的 k 行每行三个正整数 i,j,m 表示 i,j 两台计算机之间有网线联通,通畅程度为 m。

Output

一个正整数,Σ f(i,j)的最大值。

Samples

输入数据 1

5 5
1 2 8
1 3 1
1 5 3
2 4 5
3 4 2

输出数据 1

8

思路

“并且被除去网线的 Σ f(i,j)最大”,这个问题比较难解决。正难则反,发现问题可以转化为“剩下的网线Σ f(i,j)最小”,并且其他要求也都符合最小生成树的特点,可以用最小生成树做。

代码

#include<bits/stdc++.h>
using namespace std;
struct stu{
	int u,v,w;
}k[100010];
int n,m,ans,sum,l,r,g,a[100010];
int find(int x){
	if(a[x]==x)return x;
	else return a[x]=find(a[x]);//路径压缩并查集 
}
bool cmp(const stu&x,const stu&y){
	return x.w<y.w;//结构体排序 
}
int main(){
	ios::sync_with_stdio(0);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)a[i]=i;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&k[i].u,&k[i].v,&k[i].w);
		ans+=k[i].w;//ans是全部线的权值和 
	}
	sort(k+1,k+m+1,cmp);
	for(int i=1;i<=m;i++){
		l=find(k[i].u);
		r=find(k[i].v);
		if(l!=r){//祖先不同,加入生成树 
			a[l]=r;
			sum+=k[i].w;//加上除去的权值 
			g++;	
		}
		if(g==n-1)break;//够了 
	}
	cout<<ans-sum;
	return 0;
}

2.繁忙的都市

Description

城市C是一个非常繁忙的大都市,城市中的道路十分的拥挤,于是市长决定对其中的道路进行改造。

城市C的道路是这样分布的:城市中有 n 个交叉路口,有些交叉路口之间有道路相连,两个交叉路口之间最多有一条道路相连接。这些道路是双向的,且把所有的交叉路口直接或间接的连接起来了。每条道路都有一个分值,分值越小表示这个道路越繁忙,越需要进行改造。但是市政府的资金有限,市长希望进行改造的道路越少越好,于是他提出下面的要求:

(1)改造的那些道路能够把所有的交叉路口直接或间接的连通起来。

(2)在满足要求(1)的情况下,改造的道路尽量少。

(3)在满足要求(1)、(2)的情况下,改造的那些道路中分值最大值尽量小。

作为市规划局的你,应当作出最佳的决策,选择那些道路应当被修建。

Format

Input

第一行有两个整数 n,m 表示城市有 n 个交叉路口,m 条道路。

接下来 m 行是对每条道路的描述,u,v,c 表示交叉路口 u 和 v 之间有道路相连,分值为 c。(1 ≤ n ≤ 300,1 ≤ c ≤ 10000)。

Output

两个整数 s,max,表示你选出了几条道路,分值最大的那条道路的分值是多少。

Samples

输入数据 1

4 5
1 2 3
1 4 5
2 4 7
2 3 6
3 4 8

输出数据 1

3 6

思路

改一下最小生成树,如果要这条边,则更新最大值。而且最少的边数一定是n-1条,直接输出即可。

代码

#include<bits/stdc++.h>
using namespace std;
struct stu{
	int u,v,w;
}k[100010];
int n,m,ans=-1e9,l,r,g,a[100010];//ans初始化为极小值 
int find(int x){
	if(a[x]==x)return x;
	else return a[x]=find(a[x]);
}
bool cmp(const stu&x,const stu&y){
	return x.w<y.w;
}
int main(){
	ios::sync_with_stdio(0);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)a[i]=i;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&k[i].u,&k[i].v,&k[i].w);//输入
	}
	sort(k+1,k+m+1,cmp);
	for(int i=1;i<=m;i++){
		l=find(k[i].u);
		r=find(k[i].v);
		if(l!=r){
			a[l]=r;
			g++;
			ans=k[i].w;//因为是按照结构体按权值从小到大排序的,所以直接更新即可 
		}
		if(g==n-1)break; 
	}
	cout<<n-1<<" "<<ans;//输出
	return 0;
}

3.联络员

Description

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

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

Format

Input

第一行 n,m 表示Tyvj一共有 n 个管理员,有 m 个通信渠道;

第二行到 m + 1 行,每行四个非负整数,p,u,v,w。当 p = 1 时,表示这个通信渠道为必选通信渠道;当 p = 2 时,表示这个通信渠道为选择性通信渠道;u,v,w 表示本条信息描述的是 u,v 管理员之间的通信渠道,u 可以收到 v 的信息,v 也可以收到 u 的信息,w 表示费用。

Output

一行,一个整数,最小的通信费用。

Samples

输入数据 1

5 6
1 1 2 1
1 2 3 1
1 3 4 1
1 4 1 1
2 2 5 10
2 2 5 5

输出数据 1

9

思路 

一道很适合用Kruskal算法的题。这题有一定要用的边,也有可以选择的。那么我们先一定要用的边加上,然后再贪心取其他可以选择的边。

代码

#include<bits/stdc++.h>
using namespace std;
struct stu{
	int p,u,v,w;
}k[100010];
int n,m,ans,l,r,c,a[100010];
int find(int x){
	if(a[x]==x)return x;
	else return a[x]=find(a[x]);
}
bool cmp(const stu&x,const stu&y){
	return x.w<y.w;
}
int main(){
	ios::sync_with_stdio(0);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)a[i]=i;
	for(int i=1;i<=m;i++)scanf("%d%d%d%d",&k[i].p,&k[i].u,&k[i].v,&k[i].w);//输入
	sort(k+1,k+m+1,cmp);
	for(int i=1;i<=m;i++){
		l=find(k[i].u);
		r=find(k[i].v);
		if(k[i].p==1){//一定要加的边
			if(l!=r){//理论上不会出现l==r的情况,但这样写也是正确的
				a[l]=r;
				c++;	
			}
			ans+=k[i].w;//加上权值
		}
		if(c==n-1)break;
	}
	for(int i=1;i<=m;i++){
		l=find(k[i].u);
		r=find(k[i].v);
		if(l!=r&&k[i].p==2){//可以选择的边
			a[l]=r;
			c++;
			ans+=k[i].w;
		}
		if(c==n-1)break;//够了
	}
	cout<<ans;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_gxd_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值