POJ传送门

最佳比率生成树

最小化 ∑ a i ∑ b i = r \frac{\sum a_i}{\sum b_i}=r biai=r

∑ a i − r ∗ ∑ b i = 0 \sum a_i-r*\sum b_i=0 airbi=0

显然对于不同的边集, ∑ a i \sum a_i ai ∑ b i \sum b_i bi各不相同

设函数 f ( r ) = A − B ∗ r f(r)=A-B*r f(r)=ABr

不同的边集构成了不同的截距 A A A和斜率 − B -B B

斜率为负,截距为正,设这些直线的集合是 S S S

我们想求的是做靠左边的与 x x x轴的交点,那才是最小值!!

若作一条直线 x = p o s x=pos x=pos,会和 S S S中所有直线产生交点

如果 S S S中存在直线和 x = p o s x=pos x=pos的交点是负数,说明最小值在左边,往左边继续二分

如果都是正数,最小值在右边,往右边二分

所以,二分的时候求最小生成树判断,是不是所有的交点都是正数

#include <algorithm>
#include <stdio.h>
#include <iostream>
#include <math.h>
using namespace std;
const int maxn=1009;
const double eps=1e-5;
int n,pre[maxn],vis[maxn];
double s[maxn],x[maxn],y[maxn],z[maxn];
double a[maxn][maxn],b[maxn][maxn],mp[maxn][maxn];
double aabs(double x){ return x<0?-x:x;}
bool isok( double mid )
{
	for(int i=1;i<=n;i++)
	for(int j=i+1;j<=n;j++)
		mp[i][j]=mp[j][i]=a[i][j]-mid*b[i][j];
    fill(vis+1,vis+n+1,0);
    double res=0;
    s[1]=0,vis[1]=1;
    for(int i=1;i<=n;i++)s[i]=mp[1][i];
    for(int i=1;i<n;i++){
        int u=0;
        for(int j=1;j<=n;j++)if(!vis[j]&&(!u||s[j]<s[u]))u=j;
        vis[u]=1,res+=s[u];
        for(int v=1;v<=n;v++)if(!vis[v])s[v]=min(s[v],mp[u][v]);
    }
    return res<0;
}
int main()
{
	while( cin >> n && n )
	{
		double l=0,r=0;
		for(int i=1;i<=n;i++)
			scanf("%lf%lf%lf",&x[i],&y[i],&z[i]);
		for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
		{
			a[i][j] = aabs( z[i]-z[j] );
			b[i][j] = sqrt( (x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
			r = max( r,a[i][j]/b[i][j] );
		}
		double ans,mid;
		while( r>l+eps )
		{
			mid = (l+r)/2;
			if( isok(mid) )	r=mid-eps,ans=mid;	
			else	l=mid+eps;
		}
		printf("%.3lf\n",l);
	}
}