Qin Shi Huang's National Road System
Qin Shi Huang undertook gigantic projects, including the first version of the Great Wall of China, the now famous city-sized mausoleum guarded by a life-sized Terracotta Army, and a massive national road system. There is a story about the road system:
There were n cities in China and Qin Shi Huang wanted them all be connected by n-1 roads, in order that he could go to every city from the capital city Xianyang.
Although Qin Shi Huang was a tyrant, he wanted the total length of all roads to be minimum,so that the road system may not cost too many people's life. A daoshi (some kind of monk) named Xu Fu told Qin Shi Huang that he could build a road by magic and that magic road would cost no money and no labor. But Xu Fu could only build ONE magic road for Qin Shi Huang. So Qin Shi Huang had to decide where to build the magic road. Qin Shi Huang wanted the total length of all none magic roads to be as small as possible, but Xu Fu wanted the magic road to benefit as many people as possible ---- So Qin Shi Huang decided that the value of A/B (the ratio of A to B) must be the maximum, which A is the total population of the two cites connected by the magic road, and B is the total length of none magic roads.
Would you help Qin Shi Huang?
A city can be considered as a point, and a road can be considered as a line segment connecting two points.
Input
For each test case:
The first line is an integer n meaning that there are n cities(2 < n <= 1000).
Then n lines follow. Each line contains three integers X, Y and P ( 0 <= X, Y <= 1000, 0 < P < 100000). (X, Y) is the coordinate of a city and P is the population of that city.
It is guaranteed that each city has a distinct location.
Output
Sample Input
Sample Output
Source
题意:秦始皇要在n个城市之间修路,他想使得n个城市构成一个树形结构,并使得连接的路的长度和最小。这时,徐福说他有一种办法使得一条路不需要任何花销就能直接修成。秦始皇想让他修这些必要的路中最长的一条,但是徐福想修造福人口数最多的一条(即该条路连接的两个城市的人口和)。于是秦始皇为了均衡矛盾,给出了一种计算方法,将一条路两端的人口数A除以其他(n-2)条需要建造的道路的总长B,即计算A/B的比值,选取比值最大的一条修。输出该条路的比值。简而言之,该题就是要求除了徐福变的那条边既可以在秦始皇原定道路上,也可以不在,其他边都要在秦始皇原定道路上,然后要使徐福变的那条路A/B的值最大。
题解:看到秦始皇的要求,我们很自然就能想到最小生成树。所以我们先用prim求最小生成树。
为什么要用prim求最小生成树而不是kruskal呢?具体原因会在后面分析。
很显然,因为该题给出的是城市的坐标。所以我们会把每条路的长度首先都算出来,即算出了n(n-1)/2条边的长度,于是我们构成了一张完全图。我们是用这张完全图来求最小生成树的。
我们会发现,根据n的范围,完全图最多有499500条边,用kruskal也不可能TLE,但我们为什么要使用prim呢?
因为这题并不是一道裸的最小生成树,我们中间需要记录一些东西,用prim比较方便,所以不用kruskal。
我们要使A/B的值最小,可以想到如果在A固定的情况下,我们尽量使B小,于是我们有了下面的思路。
当我们求出最小生成树以后,设其值为MST,我们枚举完全图的每一条边。此时分两种情况讨论:
1、若枚举到的这条边在最小生成树上,设这条边是(i,j),我们直接计算(p[i]+p[j])/(MST-e[i][j]),即直接把这条路砍掉,按题意求值。
2、若枚举到的这条边不在最小生成树上,设这条边是(i,j),我们此时计算(p[i]+p[j])/(MST-g[i][j])。其中,g[i][j]表示从i到j的路径上最长的一条边是多少。
对于第2种情况,它的含义是我们将最小生成树上i到j的路径上最长的边砍掉,直接连上i到j的这条路径,然后这条路径由徐福的魔法来变。这样保证了道路还是树形结构,非魔法边是秦始皇的原定边,又能让这个式子的值尽量大。
通过以上分析,这题的具体算法也就明确了。接下来,我们只需要说明一下g[i][j]的求法此题就解决了。
对于g[i][j]的计算,我们可以在每一次加入一个新的点u时,计算g[u][j]=g[j][u]=max(g[pre[u]][j],dis[u]),当然j应该满足已经加入到最小生成树上。其中pre[u]表示扩展出u的前一个节点(我们如果把最小生成树看成以1为根节点的有根树,则pre[u]为u的父节点)。
同时,我们每次加入一个新节点u时,把一个bool型数组used[u][pre[u]]记为1,其他都为0,表示这条边是被最小生成树使用的。
综上所述,我们不难发现这些操作用prim算法操作比较简便,使用kruskal就没有这种特性。
再想看具体实现的见代码。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1005; 4 int t,n,p[N],pre[N]; 5 double ans,MST,e[N][N],g[N][N],x[N],y[N],dis[N]; 6 bool vis[N],used[N][N]; 7 double get_dis(double a,double b,double c,double d){ 8 return sqrt((a-c)*(a-c)+(b-d)*(b-d)); 9 } 10 void prim() 11 { 12 memset(vis,0,sizeof(vis)); 13 memset(used,0,sizeof(used)); 14 memset(g,0,sizeof(g)); 15 for (int i=1;i<=n;++i) 16 dis[i]=e[1][i],pre[i]=1; 17 for (int i=1;i<=n;++i) 18 { 19 int u=-1; 20 for (int j=1;j<=n;++j) 21 if (!vis[j]&&(u==-1||dis[j]<dis[u])) u=j; 22 MST+=dis[u]; vis[u]=1; used[u][pre[u]]=used[pre[u]][u]=1; 23 for (int j=1;j<=n;++j) 24 { 25 if (vis[j]&&j!=u) g[u][j]=g[j][u]=max(g[j][pre[u]],dis[u]); 26 if (!vis[j]&&e[u][j]<dis[j]) dis[j]=e[u][j],pre[j]=u; 27 } 28 } 29 } 30 int main() 31 { 32 scanf("%d",&t); 33 while (t--) 34 { 35 scanf("%d",&n); 36 for (int i=1;i<=n;++i) 37 scanf("%lf%lf%d",&x[i],&y[i],&p[i]); 38 for (int i=1;i<n;++i) 39 for (int j=i+1;j<=n;++j) 40 e[i][j]=e[j][i]=get_dis(x[i],y[i],x[j],y[j]); 41 MST=0; ans=-1; prim(); 42 for (int i=1;i<n;++i) 43 for (int j=i+1;j<=n;++j) 44 if (used[i][j]) ans=max(ans,(p[i]+p[j])/(MST-e[i][j])); 45 else ans=max(ans,(p[i]+p[j])/(MST-g[i][j])); 46 printf("%0.2lf ",ans); 47 } 48 return 0; 49 }