图论---最小生成树的简单应用

最小生成树是图论中常见的一个知识点,就是在一个图中找出一个最小联通块,使得权值之和最小,计算的算法主要有Prim和Kruskal算法,两个算法各有优劣,可以参考我之前的一篇博客:图论---最小生成树,最短路径_czc131的博客-CSDN博客_最小生成树最短路径,下面主要讲几个具体的应用。

目录

P2872 [USACO07DEC]Building Roads S 

P1991 无线通讯网 

P4047 [JSOI2010]部落划分


P2872 [USACO07DEC]Building Roads S 

题目链接:[USACO07DEC]Building Roads S - 洛谷 

这道题需要寻找需要添加的边的最小值,因为只需要计算添加的边,那么已有的边其实在计算的时候其实是一样的,因为不算入最终的结果,所以就相当于0,所以将已有的边距离变为0,这时候再去构造最小生成树就可以。

这一题给出的是点的坐标信息,为了使用方便所以我预处理了一遍点与点之间的距离,存入了数组。

#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define Inf 0x3f3f3f3f
#define N 1005
#define M 1005
double dis[N][N];
double ssize[N]; //这个点到当前生成树的最短距离
bool vis[N];
int n, m;
struct Node 
{
	int x, y;
}node[M];

double cal_dis(int x, int y)
{
	return sqrt(pow(node[x].x - node[y].x, 2) + pow(node[x].y - node[y].y, 2));
}

double prim(int st)//计算最小生成树权值
{
	vis[st] = 1;
	for (int i = 1; i <= n; i++)
		ssize[i] = dis[st][i];
	double ans = 0;
	while (1)
	{
		int k = -1;
		double Min = Inf;
		for (int i = 1; i <= n; i++)
			if (!vis[i] && ssize[i] <= Min)
			{
				k = i;
				Min = ssize[i];
			}
		if (k == -1)break;
		ans += Min;
		vis[k] = 1;
		for (int i = 1; i <= n; i++)
			if (!vis[i] && ssize[i] > dis[i][k])
				ssize[i] = dis[i][k];//更新到树的最短距离
	}
	return ans;
}

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
		scanf("%d%d", &node[i].x, &node[i].y);
	for(int i=1;i<=n;i++)//计算每个点之间的距离
		for (int j = 1; j <= n; j++)
		{
			dis[i][j] = cal_dis(i, j);
			dis[j][i] = dis[i][j];
		}
	int x, y;
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d", &x, &y);
		dis[x][y] = 0;
		dis[y][x] = 0;
	}
	printf("%.2lf", prim(1));
	return 0;
}

P1991 无线通讯网 

题目链接:无线通讯网 - 洛谷 

这一道题需要计算将所有哨塔连接起来所需要的收发器的最小传输距离。首先将哨塔就看做一个个点,卫星电话的存在相当于让两点的距离变为0,求生成树的最大值的最小值。在没有卫星电话时,此时就变为了求最小生成树同时最小传输距离就是此时生成树中的最大值,因为要保证距离最远的两个哨塔也可以联系; 有卫星电话情况稍微复杂,一个卫星电话没用,两个卫星电话可以连接在生成树的最大边,那么这时的答案就是第二长边,之后每多一个电话,假设有了电话之后最长边被消去,次长边为当前的最长边,那么多一个电话,我总可以把电话连在次长边的任意一点,那么这个点也进入了连通块,就可以消去次长边,继续添加电话也是一样的道理...所以最终答案就是先将边排序,有p个电话,就可以消去p-1条长边,输出第p条边的长度。

#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define N 600
#define Inf 0x3f3f3f3f
double f[N][N];
double dis[N];
int s,n; //卫星电话的个数和哨所的个数
bool vis[N];
struct Node
{
	int x, y;
}node[N];

double cal_dis(int x, int y)
{
	return sqrt(pow(node[x].x - node[y].x, 2) + pow(node[x].y - node[y].y, 2));
}

int cmp(double x, double y)
{
	return x > y;
}

double Min_dis(int st)
{
	for (int i = 1; i <= n; i++)
		dis[i] = f[st][i];
	vis[st] = 1;
	int num = 0;//答案序列长度
	double ans[N];//答案序列
	while (1)
	{
		int k = -1;
		double Min = Inf;
		for (int i = 1; i <= n; i++)
			if (!vis[i] && dis[i] < Min)
			{
				k = i;
				Min = dis[i];
			}
		if (k == -1)break;
		printf("%d %.2lf\n", k, Min);
		vis[k] = 1;
		ans[++num] = Min;
		for (int i = 1; i <= n; i++)
			if (!vis[i] && f[i][k] < dis[i])
				dis[i] = f[i][k];
	}
	sort(ans + 1, ans + num + 1,cmp);
	for (int i = 1; i <= num; i++)
		printf("%.2lf ", ans[i]);
	printf("\n");
	ans[0] = ans[1];
	return ans[s];
}

int main()
{
	scanf("%d%d", &s, &n);
	for (int i = 1; i <= n; i++)
		scanf("%d%d", &node[i].x, &node[i].y);
	for(int i=1;i<=n;i++)
		for (int j = 1; j <= n; j++)
		{
			f[i][j] = cal_dis(i,j);
			f[j][i] = f[i][j];
		}
	printf("%.2lf", Min_dis(1));
	return 0;
}

P4047 [JSOI2010]部落划分

题目链接:无线通讯网 - 洛谷[JSOI2010]部落划分 - 洛谷

这道题需要求生成树最小值的最大值,划分为了k个连通块,可以先算出最小生成树,此时是一个连通块,为了变为k个,那么需要砍断k-1条边作为k个部落,因为要距离尽量大,那么先给生成树的边排序,砍断最长的k-1条边,第k-1条也就是要求的距离。

#include<algorithm>
#include<math.h>
#include<stdio.h>
using namespace std;
#define Inf 0x3f3f3f3f
#define N 1005
double ans[N];
bool vis[N];
double f[N][N];
double dis[N];
int n, k;
struct Node
{
	int x, y;
}node[N];

double cal_dis(int x, int y)
{
	return sqrt(pow(node[x].x - node[y].x, 2) + pow(node[x].y - node[y].y, 2));
}

int cmp(double x, double y)
{
	return x > y;
}

double find_ans(int st)
{
	for (int i = 1; i <= n; i++)
		dis[i] = f[i][st];
	vis[st] = 1;
	int num = 0;
	while (1)
	{
		int k = -1;
		double Min = Inf;
		for(int i=1;i<=n;i++)
			if (!vis[i] && dis[i] < Min)
			{
				Min = dis[i];
				k = i;
			}
		if (k == -1)break;
		ans[++num] = Min;
		vis[k] = 1;
		//printf("%d %2.lf\n", k, Min);
		for (int i = 1; i <= n; i++)
			if (!vis[i] && dis[i] > f[i][k])
				dis[i] = f[i][k];
	}
	sort(ans + 1, ans + num + 1, cmp);
	return ans[k-1];
}

int main()
{
	scanf("%d%d", &n, &k);
	for (int i = 1; i <= n; i++)
		scanf("%d%d", &node[i].x, &node[i].y);
	for (int i = 1; i <= n; i++)
		for (int j = i; j <= n; j++)
		{
			f[i][j] = cal_dis(i, j);
			f[j][i] = f[i][j];
		}
	printf("%.2lf\n", find_ans(1));
	return 0;
}

这些都是比较简单的题,难的也没做了,就这样吧

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值