链接:
http://acm.hdu.edu.cn/showproblem.php?pid=4081
题目:
2 4 1 1 20 1 2 30 200 2 80 200 1 100 3 1 1 20 1 2 30 2 2 40
65.00 70.00
题目大意:
有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)的枚举加边修改,所以用更好的最小生成树算法是没有必要的。
求解次小生成树的算法:
约定:由T 进行一次可行交换得到的新的生成树所组成的集合,称为树T的邻集,记为N(T)。
定理 3:设T是图G的最小生成树,如果T1满足ω(T1)=min{ω(T’)| T’∈N(T)},则T1是G
的次小生成树。
证明:如果 T1 不是G 的次小生成树,那么必定存在另一个生成树T’,T’=T 使得
ω(T)≤ω(T’)<ω(T1),由T1的定义式知T不属于N(T),则
E(T’)\E(T)={a1,a2
1,……,at},E(T)\E(T’)={b1,b2,……,bt},其中t≥2。根据引理1 知,存在一
个排列bi1,bi2,……,bit,使得T+aj-bij仍然是G 的生成树,且均属于N(T),所以ω(aj)≥ω(bij),
所以ω(T’)≥ω(T+aj-bij)≥ω(T1),故矛盾。所以T1是图G 的次小生成树。
通过上述定理,我们就有了解决次小生成树问题的基本思路。
首先先求该图的最小生成树T。时间复杂度O(Vlog2V+E)
然后,求T的邻集中权值和最小的生成树,即图G 的次小生成树。
如果只是简单的枚举,复杂度很高。首先枚举两条边的复杂度是O(VE),再判断该交换是否
可行的复杂度是O(V),则总的时间复杂度是O(V2E)。这样的算法显得很盲目。经过简单的
分析不难发现,每加入一条不在树上的边,总能形成一个环,只有删去环上的一条边,才能
保证交换后仍然是生成树,而删去边的权值越大,新得到的生成树的权值和越小。我们可以
以此将复杂度降为O(VE)。这已经前进了一大步,但仍不够好。
回顾上一个模型——最小度限制生成树,我们也曾面临过类似的问题,并且最终采用动态规
划的方法避免了重复计算,使得复杂度大大降低。对于本题,我们可以采用类似的思想。首
先做一步预处理,求出树上每两个结点之间的路径上的权值最大的边,然后,枚举图中不在
树上的边,有了刚才的预处理,我们就可以用O(1)的时间得到形成的环上的权值最大的边。
如何预处理呢?因为这是一棵树,所以并不需要什么高深的算法,只要简单的BFS 即可。
预处理所要的时间复杂度为O(V2)。
这样,这一步时间复杂度降为O(V2)。
综上所述,次小生成树的时间复杂度为O(V2)。
结论1
次小生成树可由最小生成树换一条边得到.
证明:
可以证明下面一个强一些的结论:
T是某一棵最小生成树,T0是任一棵异于T的树,通过变换
T0 --> T1 --> T2 --> ... --> Tn (T) 变成最小生成树.
所谓的变换是,每次把Ti中的某条边换成T中的一条边, 而
且树T(i+1)的权小于等于Ti的权.
具体操作是:
step 1. 在Ti中任取一条不在T中的边uv.
step 2. 把边uv去掉,就剩下两个连通分量A和B,
在T中,必有唯一的边u'v' 连结A和B.
step 3. 显然u'v'的权比uv小 (否则,uv就应该在T中).
把u'v'替换uv即得树T(i+1).
特别地:取T0为任一棵次小生成树,T(n-1) 也就是次小生成树且
跟T差一条边. 结论1得证.
算法:
只要充分利用结论1, 即得V^2的算法. 具体如下:
step 1. 先用prim求出最小生成树T.
在prim的同时,用一个矩阵max[u][v] 记录 在T中连结任意两点u,v的唯一的
路中权值最大的那条边的权值. (注意这里).
这是很容易做到的,因为prim是每次增加一个结点s, 而设已经标号了的结点
集合为W, 则W中所有的结点到s的路中的最大权值的边就是当前加入的这条边.
step 1 用时 O(V^2).
step 2. 枚举所有不在T中的边uv, 加入边uv则必然替换权为max[u][v]的边.
故总时间为O(V^2).
代码:
#include<cstdio> #include<cstring> #include<iostream> #include<cmath> #define INF 2147483647 #define N 1005 using namespace std; double G[N][N], minCost[N], pos[N][2], path[N][N], cost[N], ratio, A, B; int pre[N], hash[N], n; bool used[N][N]; inline double getDist(double x1,double y1,double x2,double y2){ return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)); } double Prim(){ A=B=0; memset(hash, 0, sizeof(hash)); memset(used, 0, sizeof(used)); memset(path, 0, sizeof(path)); hash[1]=1; for(int i=1; i<=n; ++i){ minCost[i] = G[1][i]; pre[i] = 1; } for(int i=1; i<n; ++i){ int u=-1; for(int j=1; j<=n; ++j)if(!hash[j]){ if(u==-1 || minCost[j]<minCost[u]) u = j; } used[u][pre[u]]=used[pre[u]][u] = true; B += G[pre[u]][u]; hash[u] = 1; for(int j=1; j<=n; ++j){ if(hash[j]&&j!=u){ path[u][j]=path[j][u]=max(path[j][pre[u]], minCost[u]); } if(!hash[j]){ if(minCost[j]>G[u][j]){ minCost[j] = G[u][j]; pre[j] = u; } } } } return B; } int main(){ int T; scanf("%d",&T); while(T--){ scanf("%d",&n); memset(G, 0, sizeof(G)); for(int i=1; i<=n; ++i) scanf("%lf%lf%lf",&pos[i][0],&pos[i][1],&cost[i]); for(int i=1; i<=n; ++i){ for(int j=1; j<=n; ++j)if(i!=j){ G[i][j] = getDist(pos[i][0],pos[i][1],pos[j][0],pos[j][1]); } } Prim(); ratio = -1; for(int i=1; i<=n; ++i){ for(int j=1; j<=n; ++j)if(i!=j){ if(used[i][j]){ ratio = max(ratio, (cost[i]+cost[j])/(B-G[i][j])); } else{ ratio = max(ratio, (cost[i]+cost[j])/(B-path[i][j])); } } } printf("%.2f\n", ratio); } return 0; }
—— 生命的意义,在于赋予它意义。
原创 http://blog.csdn.net/shuangde800 , By D_Double (转载请标明)