题目:有n个村庄,村庄在不同坐标和海拔,现在要对所有村庄供水,只要两个村庄之间有一条路即可,建造水管距离为坐标之间的欧几里德距离,费用为海拔之差,现在要求方案使得费用与距离的比值最小。
思路:和普通01分数规划相同,二分或者迭代来求,建最小生成树时需要用prime算法(点数较少,边数很多,完全图)。
二分代码:
/*2204ms*/
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define inf 1e6
#define eps 1e-8
const int maxn=1010;
int n;
int x[maxn],y[maxn],h[maxn];
double dis[maxn][maxn],cost[maxn][maxn];
double dist(int i,int j)
{
return sqrt(1.0*(x[i]-x[j])*(x[i]-x[j])+1.0*(y[i]-y[j])*(y[i]-y[j]));
}
bool visited[maxn];
double w[maxn][maxn],lowcost[maxn];
int prime(double x)
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
w[i][j]=cost[i][j]-dis[i][j]*x;
for(int i=1;i<=n;i++)
lowcost[i]=w[1][i];
memset(visited, false, sizeof(visited));
lowcost[1]=0;
visited[1]=true;
double ans=0;
for(int i=1;i<n;i++)
{
double mi=inf;
int k;
for(int j=1;j<=n;j++)
if(!visited[j]&&mi>lowcost[j])
{
mi=lowcost[j];
k=j;
}
visited[k]=true;
ans+=mi;
for(int j=1;j<=n;j++)
{
if(!visited[j]&&lowcost[j]>w[k][j])
lowcost[j]=w[k][j];
}
}
return ans<=0;
}
double solve()
{
double l=0,r=inf;
while(r-l>eps)
{
double mid=(l+r)/2.0;
if(prime(mid))
r=mid;
else l=mid;
}
return l;
}
int main()
{
while(~scanf("%d",&n))
{
if(n==0) break;
for(int i=1;i<=n;i++)
scanf("%d%d%d",&x[i],&y[i],&h[i]);
int m=0;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{
dis[i][j]=dis[j][i]=dist(i,j);
cost[i][j]=cost[j][i]=1.0*abs(h[i]-h[j]);
}
printf("%.3f\n",solve());
}
return 0;
}
Dinkelbach迭代(效率比较高) 代码:
/*226ms*/
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define inf 1e6
#define eps 1e-5
const int maxn=1010;
int n;
int x[maxn],y[maxn],h[maxn];
double dis[maxn][maxn],cost[maxn][maxn];
double dist(int i,int j)
{
return sqrt(1.0*(x[i]-x[j])*(x[i]-x[j])+1.0*(y[i]-y[j])*(y[i]-y[j]));
}
bool visited[maxn];
double w[maxn][maxn],lowcost[maxn];
int pre[maxn];
double prime(double x)
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
w[i][j]=cost[i][j]-dis[i][j]*x;
for(int i=1;i<=n;i++)
{
lowcost[i]=w[1][i];
pre[i]=1;
}
memset(visited, false, sizeof(visited));
lowcost[1]=0;
visited[1]=true;
double ans1=0,ans2=0;
for(int i=1;i<n;i++)
{
double mi=inf;
int k;
for(int j=1;j<=n;j++)
if(!visited[j]&&mi>lowcost[j])
{
mi=lowcost[j];
k=j;
}
visited[k]=true;
ans1+=cost[pre[k]][k];
ans2+=dis[pre[k]][k];
for(int j=1;j<=n;j++)
{
if(!visited[j]&&lowcost[j]>w[k][j])
{
lowcost[j]=w[k][j];
pre[j]=k;
}
}
}
return ans1/ans2;
}
double solve()
{
double ans=0,L;
while(1)
{
L=ans;
ans=prime(L);
if(fabs(ans-L)<eps)
break;
}
return ans;
}
int main()
{
while(~scanf("%d",&n))
{
if(n==0) break;
for(int i=1;i<=n;i++)
scanf("%d%d%d",&x[i],&y[i],&h[i]);
int m=0;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{
dis[i][j]=dis[j][i]=dist(i,j);
cost[i][j]=cost[j][i]=1.0*abs(h[i]-h[j]);
}
printf("%.3f\n",solve());
}
return 0;
}