0/1分数规划+最小生成树
题意
有n个村庄,村庄在不同坐标和海拔,现在要对所有村庄供水,只要两个村庄之间有一条路即可,建造水管距离为坐标之间的欧几里德距离,费用为海拔之差,现在要求方案使得费用与距离的比值最小
n <= 1000
输入n行每行三个数x y z分别表示坐标与海拔
题目中所给条件,可以推出任意两点间的距离和海拔差
记$d[i][j]$表示从点i到点j的距离,$cost[i][j]$表示从点i到点j的海拔之差
对于距离$d$数组,海拔$cost$数组,可以看做一个典型的0/1分数规划
根据0/1分数规划的做法,二分枚举答案
但是二分中判断函数,不只是单单的排序
记$e[i][j]=h[i][j]-mid*cost[i][j]$
那么判断函数就是求以e数组为图的边的最小生成树
此处为完全图,Kruskal算法复杂度为$O(n^{2}logn^{2})$,外层二分再套个$logn$
复杂度无法接受
那么使用Prim算法
那么总复杂度为$O(n^{2}logn)$
#include <iostream> #include <cstdio> #include <algorithm> #include <cmath> using namespace std; int n,cost[1100][1100]; double dis[1100][1100],e[1100][1100],ans; double lowcost[1100]; bool vi[1100]; struct node { int x,y,z; }sh[1100]; bool check(double mid) { double sum=0.0; for (int i=1;i<=n;i++) { for (int j=1;j<=n;j++) { if (i==j) { e[i][j]=0; continue; } e[i][j]=(double)cost[i][j]-mid*dis[i][j];//求出e数组 } vi[i]=0; } for (int i=2;i<=n;i++) { lowcost[i]=e[1][i]; } vi[1]=1; for (int i=1;i<n;i++)//Prim算法 { double MIN; int wh; MIN=1.0e9; for (int j=2;j<=n;j++) { if (!vi[j] && lowcost[j]<MIN) { MIN=lowcost[j]; wh=j; } } vi[wh]=1; sum+=MIN; for (int j=2;j<=n;j++) { if (!vi[j] && e[wh][j]<lowcost[j]) { lowcost[j]=e[wh][j]; } } } return sum>=0.0; } int main() { while (1) { scanf("%d",&n); if (n==0) break; for (int i=1;i<=n;i++) scanf("%d%d%d",&sh[i].x,&sh[i].y,&sh[i].z); for (int i=1;i<=n;i++) { for (int j=1;j<=n;j++) { cost[i][j]=abs(sh[i].z-sh[j].z); dis[i][j]=sqrt((sh[i].x-sh[j].x)*(sh[i].x-sh[j].x)+(sh[i].y-sh[j].y)*(sh[i].y-sh[j].y)); } } double l,r,mid; l=0.0; r=1000000000.0; while (r-l>1.0e-6)//注意精度问题 { mid=(l+r)/2; if (check(mid)) { l=mid; ans=mid; } else { r=mid; } } printf("%.3f ",ans); } }