题目大意:有n个城市,秦始皇要修n-1条路连接,要求路径最短。而徐福拥有魔法可以修一条魔法道路,不需要人力和财力花费。但是这条路是人力最多的那一条。因此秦始皇给出了一个公式A/B,A为那条路两边城市的人数,B为除这条路其它要修路的路径之和。
解题思路:之前没写过次小生成树,以为不好写,看了题解之后发现不是很难的,主要是怎么去想吧!想好了就会很简单,想不到就会很难,参见了这篇博客的解法。具体的解法是:要使的A/B值最大,则需要是B尽量要小。但需要求减少哪一条边,则可以进行枚举删边,来更新答案,最后枚举所有的边后输出即可。就是次小生成树的解法。这里要注意的是可能魔法路不是MST的边,这是需要删除MST中两个点之间权值最大的哪一条边,因此Prim时要进行记录两个点之间权值最大的哪一条边。详见cod。
题目来源:http://acm.hdu.edu.cn/showproblem.php?pid=4081
code:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAXN = 1000+10;
const int INF = 0x3fffffff;
int t,n;
int pre[MAXN],vis[MAXN],people[MAXN]; //pre存储当前结点的前驱结点
double dist[MAXN],g[MAXN][MAXN],path[MAXN][MAXN]; //path存储从i到j的路径上的最大权值
bool used[MAXN][MAXN]; //记录两个结点之间的路径是否在MST中
struct point{
int x,y;
}p[MAXN];
double distan(point p1,point p2){ //距离计算公式
return sqrt(double(p1.x-p2.x)*(p1.x-p2.x)+double(p1.y-p2.y)*(p1.y-p2.y));
}
double prim(){
double ans=0;
memset(vis,0,sizeof(vis));
memset(path,0,sizeof(path));
memset(used,0,sizeof(used));
vis[1]=1;
for(int i=1;i<=n;++i){ //初始化
dist[i]=g[1][i];
pre[i]=1; //前驱结点均为1
}
for(int i=1;i<n;++i){
int u=-1;
for(int j=1;j<=n;++j)
if(!vis[j] && (u==-1 || dist[j]<dist[u])) u=j; //求最小权值边
used[u][pre[u]]=used[pre[u]][u]=true; //标记最小生成树的边
ans+=g[pre[u]][u]; //记录最小值
vis[u]=1; //访问标记
for(int k=1;k<=n;++k){
if(vis[k] && k!=u) //计算u到k之间的权值最大的边权值
path[u][k]=path[k][u]=max(path[k][pre[u]],dist[u]);
if(!vis[k] && dist[k]>g[u][k]){ //更新最小边
dist[k]=g[u][k];
pre[k]=u; //记录前驱结点
}
}
}
return ans;
}
int main(){
//freopen("input.txt","r",stdin);
scanf("%d",&t);
while(t--){
memset(g,0,sizeof(g));
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d%d%d",&p[i].x,&p[i].y,&people[i]);
for(int i=1;i<=n;++i) //算权值,建图
for(int j=1;j<=n;++j)
if(i!=j) g[i][j]=distan(p[i],p[j]);
double mst=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]) //如果在MST中
ans=max(ans,(people[i]+people[j])/(mst-g[i][j]));
else
ans=max(ans,(people[i]+people[j])/(mst-path[i][j]));
}
printf("%.2f\n",ans);
}
return 0;
}