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
#include
#include
#include
#include
#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
#include
#include
#include
#include
#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;
}