题意:空间中内有n个点,连n-1条边,使得这些边 的高度差之和比上水平距离之和最小。
最优比例生成树问题
对于一个最小生成树,可以表达为如下形式:
x1 * a1+x2 * a2+…+xm * am
其中x1 ~ xm要么为1要么为0,并且xi的和为n-1。ai是编号为i的边的权值,并且选择的点要使得图联通。
那么对于题中的问题,取一个生成树满足sum{xi * hi}/sum{xi * di}>=ans
对上式变形,得到:
sum{xi * (hi-ans * di)}>=0
很显然,这就是普通的最小生成树的形式,每条边的边权为hi-ans * di,并且由于ans有单调性,二分ans然后求最小生成树,然后判断最小生成树的和是否大于零,然后卡左右边界的值即可。
但是二分的方法会超时。因此考虑使用迭代的方法解决。
假设当前讨论到的答案是x,那么每条边的边权为hi-x * di。用变量x0记录之前算出的答案x,然后跑最小生成树,记录sumh,sumd为这次最小生成树中高度差的和以及水平距离的和,然后用sumh/sumd当作新的x值进行迭代计算。直到x和x0相比不再变小,就可以停止迭代了。
这种算法的正确性建立在x的每次迭代和上次相比会单调递减。更具体的分析参考博客:https://blog.youkuaiyun.com/chenzhenyu123456/article/details/48160209
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<stack>
#include<vector>
#include<cmath>
#include<map>
#define LL long long
using namespace std;
const int maxn=1005;
const double inf=1e10;
const double eps=1e-4;
inline void _read(int &x){
char t=getchar();bool sign=true;
while(t<'0'||t>'9')
{if(t=='-')sign=false;t=getchar();}
for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
if(!sign)x=-x;
}
int n,fa[maxn];
double x[maxn],y[maxn],z[maxn];
int getfa(int a){return a==fa[a]?a:fa[a]=getfa(fa[a]);}
struct Edge{
int from,to;
double d,h,dis;
Edge(int from,int to,double d,double h,double dis):from(from),to(to),d(d),h(h),dis(dis){}
bool operator <(const Edge& red)const{
return dis < red.dis;
}
};
vector<Edge>edges;
void addedges(int from,int to,double d,double h){
edges.push_back(Edge(from,to,d,h,0));
}
double get_dist(int i,int j){
return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
}
int main(){
_read(n);
for(int i=1;i<=n;i++)cin>>x[i]>>y[i]>>z[i];
for(int i=1;i<n;i++)
for(int j=i+1;j<=n;j++)
addedges(i,j,get_dist(i,j),fabs(z[i]-z[j]));
double cur=0,ans=inf;
while(1){
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=0;i<edges.size();i++)edges[i].dis=edges[i].h-edges[i].d*cur;
sort(edges.begin(),edges.end());
double sum1=0,sum2=0,sum=0;
for(int i=0;i<edges.size();i++){
int u=edges[i].from,v=edges[i].to;
int fu=getfa(u),fv=getfa(v);
if(fu!=fv)fa[fu]=fv,sum+=edges[i].dis,sum1+=edges[i].h,sum2+=edges[i].d;
}
cur=sum1/sum2;
if(fabs(cur-ans)<=eps)break;
ans=cur;
}
printf("%.3lf",ans);
}