Time Limit: 3000MS | Memory Limit: 65536K | |
Total Submissions: 22289 | Accepted: 6253 |
Description
Input
Output
Sample Input
4 0 0 0 0 1 1 1 1 2 1 0 3 0
Sample Output
1.000
题意:有N个村庄。这些村庄在不同坐标和海拔,现在要对所有村庄供水,每两个村庄之间只有一条通道即可。建造通道的距离为村庄之间的欧几里德距离,费用则为村庄间的海拔之差。现在要求一种方案使得总费用与总距离的比值最小,问你最小的比值。
用cost[i][j]表示i、j村庄修造通道的费用,len[i][j]为i、j村庄修造通道的距离。
方法一:二分 2235ms
设最小比值o = sigma(cost[i][j]) / sigma(len[i][j])。
构造函数Map[i][j] = cost[i][j] - o * len[i][j]。
则取最小比值时,有sigma(Map[i][j]) = 0。(其中Map[i][j]里面的i->j这条边是当前生成树的边)
实现过程:
枚举比值mid,求出所有的Map值。然后跑一次prim,求出构造最小生成树的Map值总和ans。当枚举的值mid就是最优值o的时候,有sigma(Map[i][j]) = 0。在枚举过程中,若ans < 0,说明o值过大;若ans > 0,说明o值过小。
AC代码:
#include <cstdio> #include <cstring> #include <cmath> #include <cstdlib> #include <algorithm> #define MAXN 1010 #define INF 0x3f3f3f3f #define eps 1e-8 using namespace std; int N; double Map[MAXN][MAXN]; struct Node { double x, y, h; }; Node num[MAXN]; double cost[MAXN][MAXN], len[MAXN][MAXN]; double dis(Node a, Node b) { return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); } double Max; void getMap()//求出cost 和 len { Max = 0; for(int i = 0; i < N; i++) { for(int j = i+1; j < N; j++) { cost[i][j] = cost[j][i] = fabs(num[i].h - num[j].h); len[i][j] = len[j][i] = dis(num[i], num[j]); Max = max(Max, cost[i][j] / len[i][j]); } } } double low[MAXN]; bool vis[MAXN]; double prime()//求最小生成树 { for(int i = 0; i < N; i++) { vis[i] = false; low[i] = Map[0][i]; } vis[0] = true; double ans = 0; for(int i = 1; i < N; i++) { double Min = INF; int next = 0; for(int j = 0; j < N; j++) { if(!vis[j] && Min > low[j]) { next = j; Min = low[j]; } } if(Min == INF) break; vis[next] = true; ans += Min; for(int j = 0; j < N; j++) { if(!vis[j]) low[j] = min(low[j], Map[next][j]); } } return ans; } bool judge(double o) { for(int i = 0; i < N; i++) { for(int j = i+1; j < N; j++)//重新计算Map值 Map[i][j] = Map[j][i] = cost[i][j] - o * len[i][j]; } return prime() >= 0;//判断构造最小生成树的 Map值总和是否大于或等于0 } int main() { while(scanf("%d", &N), N) { for(int i = 0; i < N; i++) scanf("%lf%lf%lf", &num[i].x, &num[i].y, &num[i].h); getMap(); double l = 0, r = Max, mid; while(r - l >= eps) { mid = (l + r) / 2; if(judge(mid)) l = mid; else r = mid; } printf("%.3lf\n", l); } return 0; }
刚学习,为了加深印象,就写了一点自己的见解,不对的地方欢迎指正。
实现过程:
设x为当前生成树的最优比值
1,先给x赋初值(任意N-1条边的花费总和与长度总和的比值),用x值构造Map值,Map[i][j] = cost[i][j] - x * len[i][j],并用变量x0存储x的值;
2,以构造出的各边的Map值,求最小生成树。在这里用两个变量sumcost和sumlen记录最小生成树中所有边的总花费和总长度。
3,结果sumcost / sumlen是我们用x0(即先前的x)求出的更优的值,更新x = sumcost / sumlen。
4,一直重复执行1、2、3,直到x >= x0 —— 即求出的更优值x 没有上一次的值x0小。
验证上述做法的正确性——关键在于我们能够证出x的值在上述的过程中是单调递减的。
首先我们考虑任选N-1条边的x值,对于选出的边一定会有sigma(cost[i][j]) - x * sigma(len[i][j]) = 0。即sigma(Map[i][j]) = 0。
在用x值求出所有的Map值之后,我们按Map值从小到大重新选出N-1条边,这时sigma(Map[i][j])必<=0。
相应的对于公式sigma(cost[i][j]) - x * sigma(len[i][j]) <= 0 ——> sigma(cost[i][j]) / sigma(len[i][j]) <= x
上述证明只证明了一次推导的单调性,但用这个思路足以证明所有推导的单调性。
看网上很多人直接把x赋值为0,然后求解。表示对于这个处理不是很理解,希望有大牛解惑。
AC代码:
#include <cstdio> #include <cstring> #include <cmath> #include <cstdlib> #include <algorithm> #define MAXN 1010 #define INF 0x3f3f3f3f #define eps 1e-8 using namespace std; int N; double Map[MAXN][MAXN]; struct Node { double x, y, h; }; Node num[MAXN]; double cost[MAXN][MAXN], len[MAXN][MAXN]; double dis(Node a, Node b) { return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); } void getMap()//求出cost 和 len { for(int i = 0; i < N; i++) { for(int j = i+1; j < N; j++) { cost[i][j] = cost[j][i] = fabs(num[i].h - num[j].h); len[i][j] = len[j][i] = dis(num[i], num[j]); } } } double low[MAXN]; bool vis[MAXN]; int pre[MAXN];//记录该点在最小生成树中的前驱 double prime()//求最小生成树 { for(int i = 0; i < N; i++) { vis[i] = false; low[i] = Map[0][i]; pre[i] = 0; } vis[0] = true; double sumcost = 0, sumlen = 0; for(int i = 1; i < N; i++) { double Min = INF; int next = 0; for(int j = 0; j < N; j++) { if(!vis[j] && Min > low[j]) { next = j; Min = low[j]; } } vis[next] = true; sumcost += cost[pre[next]][next];//记录花费 sumlen += len[pre[next]][next];//记录长度 for(int j = 0; j < N; j++) { if(!vis[j] && low[j] > Map[next][j]) { low[j] = Map[next][j]; pre[j] = next; } } } return sumcost / sumlen; } void newMap(double o) { for(int i = 0; i < N; i++) { for(int j = i+1; j < N; j++)//重新计算Map值 Map[i][j] = Map[j][i] = cost[i][j] - o * len[i][j]; } } int main() { while(scanf("%d", &N), N) { for(int i = 0; i < N; i++) scanf("%lf%lf%lf", &num[i].x, &num[i].y, &num[i].h); getMap(); double x0; double sumcost = 0, sumlen = 0; for(int i = 1; i < N; i++)//任选N-1条边 sumcost += cost[0][i], sumlen += len[0][i]; double x = sumcost / sumlen; while(1) { x0 = x; newMap(x);//按x的值 重新计算Map值 x = prime();//更新 if(fabs(x0 - x) < eps)//新值没有减少 break; } printf("%.3lf\n", x); } return 0; }