[关闭]
@DATASOURCE 2015-09-04T14:24:12.000000Z 字数 4084 阅读 2245

最优比率生成树

图论 生成树


简述

引入:传说在2005年北京现场赛的赛场上,楼教主在5分钟搞定一题后第25分钟搞定了这个题,让所有人都以为这是一道水题,带坏了全场的节奏,200多次提交只有8个人过, 最终顺利夺冠。

我们设一个顶点数为n,边数为m的无环连通图G,其中 cost[i] 表示是第i条边花费的代价,benefit[i] 表示第i条边获得的收益,现要求这个图的一个生成树,使得这棵树的总花费与总收益的比值最小。这个问题就是解决最优比率生成树的问题。在实际问题当中,我们会经常遇到如修建道路要考虑收益和开销,使得代价比最小的问题。

算法

x[i] 等于 10 , 表示边 e[i] 是否属于生成树. 则我们所求的比率

r=(benefit[i]x[i])/(cost[i]x[i]),0i<m
为了使 r 最大, 设计一个子问题---> 让
z=(benefit[i]x[i])r(cost[i]x[i])=(d[i]x[i]),(d[i]=benefit[i]lcost[i])
并记为 z(r) .

然后明确两个性质 :
1. z单调递减
证明: 因为cost为正数, 所以z随l的减小而增大.
2. z( max(r) ) = 0
证明: 若

z(max(r))<0
(benifit[i]x[i])max(r)(cost[i]x[i])<0
可化为
max(r)<max(r)
矛盾;
z(max(r))>=0
, 根据性质1, 当 z = 0 时r最大.

这样上面的问题就转化为了求 z(r) 的零点问题, 方法有以下两个:

A、迭代法:由上面的分析可知:
z(r) > 0 时,rate 值无效,而 rateNex 有效且z(rateNex) <= 0;
z(rate) < 0 时,rateNex < rate,又 z(rate) 为单调递减函数,故 0 >= z(rateNex) > z(rate)
故迭代过程向 z(rate) == 0 收敛,又由有限节点的完全图其生成树只有有限个可知迭代次数必为有限值。

B、二分法:在定出一个搜索范围和精度之后(以[0,MAXRATE]为例),我们就可以使用二分法进行搜索:对于当前的搜索区间[low,high],mid = (low + high) / 2,根据 z(mid) 的值及 z(rate) 的增减性,对搜索区间进行更新:
从而在经过有限次搜索之后便能找到所求比率。

例题 POJ 2728


  • 题意: 有n个村庄要连在一起,村与村之间的长度为他们之间的水平距离,连在一起的花费是两村的高度差。现求所花费和与长度和之比最小

    • 输入: n 和 n 个村庄的三维坐标
    • 输出: 花费和与长度和的最小比值
  1. // 二分
  2. #include <cmath>
  3. #include <cstdio>
  4. #include <iostream>
  5. #include <algorithm>
  6. #define ABS(x) ((x) > 0 ? (x) : (-(x)))
  7. using namespace std;
  8. const double eps = 1e-4;
  9. const int MAXN = 1010;
  10. const int MAX_INT = (1 << 29);
  11. struct Point{
  12. double x, y, h;
  13. double CalDis(const Point &a){
  14. return sqrt((a.x - x) * (a.x - x) + (a.y - y) * (a.y - y));
  15. }
  16. double CalHig(const Point &a){
  17. return ABS(h - a.h);
  18. }
  19. };
  20. Point a[MAXN];
  21. int n;
  22. double mp[MAXN][MAXN];
  23. double dis[MAXN][MAXN];
  24. double cost[MAXN][MAXN];
  25. bool vis[MAXN];
  26. double dist[MAXN];
  27. double prim(int s){
  28. memset(vis, false, sizeof(vis));
  29. fill(dist, dist + MAXN, MAX_INT);
  30. vis[s] = true;
  31. dist[s] = 0;
  32. double mi;
  33. int mip = s;
  34. double ans = 0;
  35. for(int i = 0; i < n; i++)
  36. dist[i] = mp[s][i];
  37. for(int i = 1; i < n; i++){
  38. mi = MAX_INT;
  39. for(int j = 0; j < n; j++)
  40. if(!vis[j] && mi > dist[j]) mi = dist[j], mip = j;
  41. ans += mi;
  42. vis[mip] = true;
  43. for(int j = 0; j < n; j++)
  44. if(!vis[j] && dist[j] > mp[mip][j])
  45. dist[j] = mp[mip][j];
  46. }
  47. return ans;
  48. }
  49. bool check(double mid){
  50. for(int i = 0; i < n; i++){
  51. for(int j = i + 1; j < n; j++){
  52. mp[i][j] = mp[j][i] = cost[i][j] - mid * dis[i][j];
  53. }
  54. }
  55. double mst = prim(0);
  56. if(mst > eps) return true;
  57. else return false;
  58. }
  59. double solve(){
  60. double l = 0.0, r = 100.0, mid;
  61. while(r - l > eps){
  62. mid = (l + r) / 2.0;
  63. if(check(mid)) l = mid;
  64. else r = mid;
  65. }
  66. return l;
  67. }
  68. int main(){
  69. while(scanf("%d", &n) != EOF){
  70. if(!n) break;
  71. for(int i = 0; i < n; i++)
  72. scanf("%lf%lf%lf", &a[i].x, &a[i].y, &a[i].h);
  73. fill(dis[0], dis[MAXN], MAX_INT);
  74. for(int i = 0; i < n; i++)
  75. for(int j = i + 1; j < n; j++){
  76. dis[i][j] = dis[j][i] = a[i].CalDis(a[j]);
  77. cost[i][j] = cost[j][i] = a[i].CalHig(a[j]);
  78. }
  79. double ans = solve();
  80. printf("%.3f\n", ans);
  81. }
  82. return 0;
  83. }
  1. // 牛顿迭代法
  2. #include <cmath>
  3. #include <cstdio>
  4. #include <iostream>
  5. #include <algorithm>
  6. #define ABS(x) ((x) > 0 ? (x) : (-(x)))
  7. using namespace std;
  8. const double eps = 1e-5;
  9. const int MAXN = 1010;
  10. const double MAX_INT = (1 << 29) / 1.0;
  11. struct Point{
  12. double x, y, h;
  13. double CalDis(const Point &a){
  14. return sqrt((a.x - x) * (a.x - x) + (a.y - y) * (a.y - y));
  15. }
  16. double CalHig(const Point &a){
  17. return ABS(h - a.h);
  18. }
  19. };
  20. int n;
  21. int pre[MAXN];
  22. Point a[MAXN];
  23. double mp[MAXN][MAXN];
  24. double dis[MAXN][MAXN];
  25. double cost[MAXN][MAXN];
  26. bool vis[MAXN];
  27. double dist[MAXN];
  28. double prim(){
  29. int s = 0;
  30. memset(vis, false, sizeof(vis));
  31. memset(pre, 0, sizeof(pre));
  32. fill(dist, dist + MAXN, MAX_INT);
  33. vis[s] = true;
  34. dist[s] = 0.0;
  35. int mip = s;
  36. double mi = 0.0, dissum = 0.0, costsum = 0.0;
  37. for(int i = 1; i < n; i++){
  38. for(int j = 0; j < n; j++)
  39. if(!vis[j] && dist[j] > mp[mip][j]){
  40. dist[j] = mp[mip][j];
  41. pre[j] = mip;
  42. }
  43. mi = MAX_INT;
  44. for(int j = 0; j < n; j++)
  45. if(!vis[j] && mi > dist[j]) mi = dist[j], mip = j;
  46. vis[mip] = true;
  47. dissum += dis[mip][pre[mip]];
  48. costsum += cost[mip][pre[mip]];
  49. }
  50. return costsum / dissum;
  51. }
  52. inline void change(double mid){
  53. for(int i = 0; i < n; i++)
  54. for(int j = i + 1; j < n; j++)
  55. mp[i][j] = mp[j][i] = cost[i][j] - mid * dis[i][j];
  56. }
  57. double solve(){
  58. double last = 0.0, cur = -1.0;
  59. while(ABS(cur - last) > eps){
  60. last = cur;
  61. change(last);
  62. cur = prim();
  63. }
  64. return last;
  65. }
  66. int main(){
  67. while(scanf("%d", &n) != EOF){
  68. if(!n) break;
  69. for(int i = 0; i < n; i++)
  70. scanf("%lf%lf%lf", &a[i].x, &a[i].y, &a[i].h);
  71. for(int i = 0; i < n; i++)
  72. for(int j = i + 1; j < n; j++){
  73. dis[i][j] = dis[j][i] = a[i].CalDis(a[j]);
  74. cost[i][j] = cost[j][i] = a[i].CalHig(a[j]);
  75. }
  76. for(int i = 0; i < n; i++)
  77. dis[i][i] = cost[i][i] = 0.0;
  78. double ans = solve();
  79. printf("%.3f\n", ans);
  80. }
  81. return 0;
  82. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注