hdu4081 Qin Shi Huang's National Road System--次小生成树

原题链接: http://acm.hdu.edu.cn/showproblem.php?pid=4081


题目大意:
有n个城市,秦始皇要修用n-1条路把它们连起来,要求从任一点出发,都可以到达其它的任意点。秦始皇希望这所有n-1条路长度之和最短。然后徐福突然有冒出来,说是他有魔法,可以不用人力、财力就变出其中任意一条路出来。
秦始皇希望徐福能把要修的n-1条路中最长的那条变出来,但是徐福希望能把要求的人力数量最多的那条变出来。对于每条路所需要的人力,是指这条路连接的两个城市的人数之和。
最终,秦始皇给出了一个公式,A/B,A是指要徐福用魔法变出的那条路所需人力, B是指除了徐福变出来的那条之外的所有n-2条路径长度之和,选使得A/B值最大的那条。


分析与总结
为了使的A/B值最大,首先是需要是B尽量要小,所以可先求出n个城市的最小生成树。然后,就是决定要选择那一条用徐福的魔法来变。
因此,可以枚举每一条边,假设最小生成树的值是MinMST, 而枚举的那条边长度是w[i][j],  如果这一条边已经是属于最小生成树上的,那么最终式子的值是A/(MinMST-w[i][j])。如果这一条不属于最小生成树上的, 那么添加上这条边,就会有n条边,那么就会使得有了一个环,为了使得它还是一个生成树,就要删掉环上的一棵树。 为了让生成树尽量少,那么就要删掉除了加入的那条边以外,权值最大的那条路径。 假设删除的那个边的权值是path[i][j], 那么就是A/(MinMST-path[i][j]).
解这题的关键也在于怎样求出次小生成树。


#define _CRT_SECURE_NO_DEPRECATE 

#include<iostream>
#include<cmath>
#include<vector>
#include<algorithm>

using namespace std;

double dis[1005][1005];//两城市距离
double lowCost[1005];//
double maxx[1005][1005];//保存最小树上i到j上的最大距离
double node[1005][2];//坐标
double num[1005];//每个城市的人数
int pre[1005];//保存上一节点
bool used[1005][1005];//标记该边是否加入最小生成树
bool vis[1005];
double B;//生成树权值
int n;

double getDis(double x1, double y1, double x2, double y2)
{
	return sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2));
}

void prim()
{
	memset(used, 0, sizeof(used));
	memset(vis, 0, sizeof(vis));

	B = 0.0;
	for (int i = 1; i <= n; i++)
	{
		lowCost[i] = dis[1][i];//默认1为起点
		pre[i] = 1;
	}
	vis[1] = 1;

	for (int i = 1; i < n; i++)
	{
		int minPos;
		double minn = 99999999;
		for (int j = 1; j <= n; j++)
		{
			if (!vis[j] && lowCost[j] < minn)
			{
				minn = lowCost[j];
				minPos = j;
			}
		}
		
		B += minn;
		used[minPos][pre[minPos]] = used[pre[minPos]][minPos] = 1;
		vis[minPos] = 1;

		for (int j = 1; j <= n; j++)
		{
			if (minPos != j&&vis[j])//该点已加入生成树
				maxx[j][minPos] = maxx[minPos][j] = max(maxx[j][pre[minPos]], lowCost[minPos]);
			

			if (!vis[j] && lowCost[j] > dis[minPos][j])
			{
				lowCost[j] = dis[minPos][j];
				pre[j] = minPos;
			}
			
		}

	}
}

int main()
{
	int T;

	scanf("%d", &T);
	while (T--)
	{
		scanf("%d", &n);

		for (int i = 1; i <= n; i++)
			scanf("%lf%lf%lf", &node[i][0], &node[i][1], &num[i]);

		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++)
				if (i != j)
					dis[i][j] = getDis(node[i][0], node[i][1], node[j][0], node[j][1]);

		prim();

		double ans = -1;
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= n; j++)
			{
				if (i != j)
				{
					if (used[i][j])
						ans = max(ans, (num[i] + num[j]) / (B - dis[i][j]));
					else
						ans = max(ans, (num[i] + num[j]) / (B - maxx[i][j]));
				}
				
			}
		}

		printf("%.2lf\n", ans);
	}

	return 0;
}






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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值