题目大意:秦始皇修路,要求n个城市连通,然后有一个人可以帮忙修一条不花费的路,这个人希望这条路连接的两个城市的人数总和尽量大,所以要求这条魔法路连接的两个城市人数总和除以秦始皇需要修的路的花费这个值最大。
和求次小生成树的代码很类似,先说一下思路,先求最小生成树,然后枚举每条边当做魔法路,则分子确定了(两个城市人数总和),则只需修建其他路花费最小即可,①若该魔法路是最小生成树里的,则直接去掉即可,②若不是最小生成树里的,则添加魔法路后形成了一个环,只需将环上属于最小生成树的边中最大的那条去掉即可。
然后说明一下为什么其他边必须是最小生成树里的,除魔法路若有不属于mst的边,则把它去掉形成两个集合u,v,换成mst中连接u,v的边,可以得到更优解。
#include <stdio.h> #include <string.h> #include <math.h> int n,p[1010],vis[1010][1010],cap[1010]; double map[1010][1010],max[1010][1010],low[1010]; /*max[i][j]表示最小生成树中从i到j的路径中最长的边,map是边,p[i]是prim每次选进去的边一个节点(在之前选进去的集合中的节点) 等于一表示有边但未选入最小生成树,等于2表示选入了最小生成树,cap是每个城市的人数*/ double MAX(double a,double b) { if(a>b) return a; else return b; } double dis(int x1,int y1,int x2,int y2) { return sqrt( (x1-x2)*(x1-x2)*1.0+(y1-y2)*(y1-y2)*1.0 ); } double prim(int u) { int i,v,j; double cost=0,min; for(j=1;j<=n;j++) { low[j]=map[j][u]; p[j]=u; } low[u]=-1; for(j=1;j<=n-1;j++) { min=50000; for(i=1;i<=n;i++) { if(low[i]!=-1&&min>low[i]) { min=low[i]; v=i; } } low[v]=-1; cost+=min; vis[v][p[v]]=vis[p[v]][v]=2; for(i=1;i<=n;i++) if(i!=v) max[i][v]=max[v][i]=MAX(min,max[i][p[v]]);/*每次选入一个边都更新一遍max,动态规划*/ for(i=1;i<=n;i++) { if(low[i]!=-1&&map[v][i]<low[i]) { low[i]=map[v][i]; p[i]=v; } } } return cost; } int main() { int t,i,j,k,x[1010],y[1010];/*存放坐标*/ double sum,w; double ans; scanf("%d",&t); while(t--) { ans=0; scanf("%d",&n); memset(max,0,sizeof(max)); for(j=1;j<=n;j++) for(k=1;k<=n;k++) { map[j][k]=50000; vis[j][k]=1; } for(i=1;i<=n;i++) scanf("%d%d%d",&x[i],&y[i],&cap[i]); for(i=1;i<=n;i++) for(j=1;j<=n;j++) { if(i==j) continue; w=dis(x[i],y[i],x[j],y[j]); map[i][j]=map[j][i]=w; } sum=prim(1); for(j=1;j<=n;j++) for(k=1;k<=n;k++) { if(j==k) continue; if(vis[j][k]==1&&ans<(cap[j]+cap[k])*1.0/((sum-max[j][k])*1.0)) { ans=(cap[j]+cap[k])*1.0/(sum-max[j][k])*1.0; } if(vis[j][k]==2&&ans<(cap[j]+cap[k])*1.0/((sum-map[j][k])*1.0)) ans=(cap[j]+cap[k])*1.0/(sum-map[j][k])*1.0; } printf("%.2lf\n",ans); } return 0; }