版权声明:虽然我只是个小蒟蒻但转载也请注明出处哦 https://blog.csdn.net/weixin_42557561/article/details/82708770
【小知识】
(一不小心被自己秀到了……居然忘记了Prim算法)结论:Prim算法主要用于稠密图,尤其是完全图的最小生成树的求解
大概就是对于一个完全图(每两个点都有一条边),其边数肯定超级多,那么使用Kruskal算法(按边枚举)显然不现实,我们就使用Prim,枚举顶点
分析
这就是相当于任意两个点之间有一条连线,这条边上有其对应的成本(两点高度之差)和长度(欧几里得距离),最后 要求 所有的点连通,并且选出来的这些边的总成本比上其总长度,值最小
和之前那道01分数规划比较相似(就是因为相似,我才傻逼的把二分上界仍然定义为1……omg,都不动脑壳思考一下),但不同的是这次我们要求的是最小值,令 min r = ,(r 即最后所求的最优解,为了方便我们就用a(x)来代表此时的分子,b(x)代表分母,再定义一个函数 ,然后胡搞证明一下这个的单调性,就可以二分来做啦,
每次二分出来一个 r 就来验证一下,将每一条边的权值都重新更新,然后用Prim求最小生成树,将最后的和与0做比较,确定二分上下界的调整
代码
#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#define N 2009
#define eps 1e-10
#define in read()
#define inf 1e9
using namespace std;
inline int read(){
char ch;int res=0;
while((ch=getchar())<'0'||ch>'9');
while(ch>='0'&&ch<='9'){
res=(res<<3)+(res<<1)+ch-'0';
ch=getchar();
}
return res;
}
int n,x[N],y[N],z[N];
double dis[N][N],cost[N][N],w[N];
bool v[N];
bool check(double r){
for(int i=1;i<=n;++i) w[i]=cost[i][1]-r*dis[i][1];//更新
memset(v,0,sizeof(v));
v[1]=1;
double res=0;
for(int i=2;i<=n;++i){
double minn=1e9;
int pos;
for(int j=1;j<=n;++j) if(!v[j]&&w[j]<minn) minn=w[j],pos=j;
v[pos]=1;
res+=minn;
for(int j=1;j<=n;++j){
if(v[j]) continue;
if(w[j]>cost[pos][j]-r*dis[pos][j])
w[j]=cost[pos][j]-r*dis[pos][j];
}
}
return res>0.0;
}
int main(){
while((scanf("%d",&n))!=EOF){
if(n==0) break;
int i,j;
for(i=1;i<=n;++i){ x[i]=in;y[i]=in;z[i]=in; }
for(i=1;i<=n;++i)
for(j=1;j<=n;++j){
dis[i][j]=dis[j][i]=(double)sqrt((double)(x[i]-x[j])*(x[i]-x[j])+(double)(y[i]-y[j])*(y[i]-y[j]));
cost[i][j]=cost[j][i]=(double)abs(z[i]-z[j]);
}
double l=0,r=100;//开大一点也可以的
while(r-l>1e-10){
double mid=(l+r)/2.0;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.3f\n",l);
}
return 0;
}