hdu 4081 Qin Shi Huang's National Road System(次小生成树)

题目大意:

有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]).

解这题的关键也在于怎样求出次小生成树。

以下摘自网上资料:
[次小生成树]
类比上述次短路径求法,很容易想到一个“枚举删除最小生成树上的每条边,再求最小生成树”的直观解法。
如果用Prim+堆,每次最小生成树时间复杂度为O(N*log(N+M) + M),枚举删除有O(N)条边,
时间复杂度就是O(N^2*log(N+M) + N*M),当图很稠密时,接近O(N^3)。这种方法简易直观,但我们有一个更简单,而且效率更高的O(N^2+M)的解法,下面介绍这种方法。
首先求出原图最小生成树,记录权值之和为MinST。枚举添加每条不在最小生成树上的边(u,v),加上以后一定会形成一个环。找到环上权值第二大的边(即除了(u,v)以外的权值最大的边),把它删掉,计算当前生成树的权值之和。取所有枚举修改的生成树权值之和的最小值,就是次小生成树。
具体实现时,更简单的方法是从每个节点i遍历整个最小生成树,定义F[j]为从i到j的路径上最大边的权值。遍历图求出F[j]的值,然后对于添加每条不在最小生成树中的边(i,j),新的生成树权值之和就是MinST + w(i,j) – F[j],记录其最小值,则为次小生成树。
该算法的时间复杂度为O(N^2 + M)。由于只用求一次最小生成树,可以用最简单的Prim,时间复杂度为O(N^2)。
算法的瓶颈不在求最小生成树,而在O(N^2+M)的枚举加边修改,所以用更好的最小生成树算法是没有必要的。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const double inf=1000000.0;
struct In{
	int x,y;
};
In num[1010];
double map[1010][1010],dis[1010],path[1010][1010];//path求得是i到j的最大边权值 
int used[1010],n,vis[1010][1010],pre[1010],cost[1010]; //pre记录前驱  
double Prim(){
	int i,j,k;
	double ans,temp;
	for(i=1;i<=n;i++){
		dis[i]=inf;
		pre[i]=1;
	}
	ans=dis[1]=0.0;
	for(i=1;i<=n;i++){
		temp=inf;
		for(j=1;j<=n;j++){
			if(!used[j]&&dis[j]<temp){
				temp=dis[j];
				k=j;
			}
		}
		ans+=map[pre[k]][k];//计算最小生成树之和 
		used[k]=1;
		vis[k][pre[k]]=vis[pre[k]][k]=1;//保存在最小生成树里的边 
		for(j=1;j<=n;j++){
			if(used[j]&&j!=k){//从k到j这条路径上最大边的权值,dp思想 
				path[j][k]=path[k][j]=max(path[j][pre[k]],dis[k]);
			}
			if(!used[j]&&dis[j]>map[k][j]){//更新相邻节点的距离 
				dis[j]=map[k][j];
				pre[j]=k;//记录前驱 
			}
		}
	}	
	return ans;
}
double SecondTree(){//次小生成树 
	double m,val;
	int i,j;
	m=Prim();
	val=-1;
	for(i=1;i<=n;i++){
		for(j=1;j<=n;j++){
			if(i!=j){
				if(vis[i][j]){
					val=max(val,(cost[i]+cost[j])/(m-map[i][j]));
				}else{
					val=max(val,(cost[i]+cost[j])/(m-path[i][j]));//删除权值最大的路径 
				}
			}
		}
	}
	return val; 
} 
int main(){
	int t,i,j,s,v;
	scanf("%d",&t);
	while(t--){
		memset(num,0,sizeof(num));
		memset(used,0,sizeof(used));
		memset(vis,0,sizeof(vis));
		memset(map,0,sizeof(map));
		memset(path,0,sizeof(path));
		scanf("%d",&n);
		for(s=0,i=1;i<=n;i++){
			scanf("%d%d%d",&num[i].x,&num[i].y,&cost[i]);
		}
		for(i=1;i<=n;i++){
			for(j=i+1;j<=n;j++){
				map[i][j]=map[j][i]=sqrt(((num[i].x-num[j].x)*(num[i].x-num[j].x)+(num[i].y-num[j].y)*(num[i].y-num[j].y))*1.0);
				//printf("%.2lf\n",map[i][j]);
			}
		}
		
		printf("%.2lf\n",SecondTree());
	}
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值