0/1分数规划 生成树 沙漠之王 题解
作者:互联网
题目描述
大卫大帝刚刚建立了一个沙漠帝国,为了赢得他的人民的尊重,他决定在全国各地建立渠道,为每个村庄提供水源。
与首都相连的村庄将得到水资源的浇灌。
他希望构建的渠道可以实现单位长度的平均成本降至最低。
换句话说,渠道的总成本和总长度的比值能够达到最小。
他只希望建立必要的渠道,为所有的村庄提供水资源,这意味着每个村庄都有且仅有一条路径连接至首都。
他的工程师对所有村庄的地理位置和高度都做了调查,发现所有渠道必须直接在两个村庄之间水平建造。
由于任意两个村庄的高度均不同,所以每个渠道都需要安装一个垂直的升降机,从而使得水能够上升或下降。
建设渠道的成本只跟升降机的高度有关,换句话说只和渠道连接的两个村庄的高度差有关。
需注意,所有村庄(包括首都)的高度都不同,不同渠道之间不能共享升降机。
输入格式
输入包含多组测试数据。
输入
每组测试数据第一行包含整数 \(N\),表示村庄(包括首都)的总数目。
接下来 \(N\) 行,每行包含三个整数 \(x\),\(y\),\(z\) ,描述一个村庄的地理位置,\((x,y)\) 为该村庄的位置坐标,z 为该村庄的地理高度。
第一个被描述的村庄即为首都。
当输入一行为 \(0\) 时,表示输入终止。
输出
每组数据输出一个结果,每个结果占一行。
结果为一个保留三位小数的实数,表示渠道的总成本和总长度的比值的最小值。
数据范围:\(2 \le N \le 1000\), \(0 \le x,y \le 10000\) 。
首先说一下为什么是分数规划。
根据题面,有 \(min_z = \frac{\sum w}{\sum L}\)
\(\sum w - \sum L \times min_z = 0\)
设函数 \(F(x)=\sum w - \sum L \times z\)
得知我们需要判断当前生成树的答案与正解的关系,思考收敛方法。
单调函数,可以二分处理,还可以使用迭代算法。
二分:
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int x[N],y[N],w[N],n;
double dis[N];
bool st[N];
double calc(int a,int b)
{
return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));
}
bool check(double mid)
{
for(int i=1;i<=1000;i++) dis[i]=DBL_MAX;
memset(st,0,sizeof st);
dis[1]=0.0;
double res=0;
for(int i=1;i<=n;i++)
{
double maxx=DBL_MAX;
int k=1;
for(int j=1;j<=n;j++)
if(!st[j] && maxx>dis[j]) maxx=dis[j],k=j;
st[k]=1,res+=dis[k];
for(int j=1;j<=n;j++)
{
if(!st[j] && dis[j] > fabs(w[k]-w[j])-mid*calc(k,j)+1e-6)
dis[j]=fabs(w[k]-w[j])-mid*calc(k,j);
}
}
return res>=0.0;
}
int main()
{
while(~scanf("%d",&n) && n)
{
for(int i=1;i<=n;i++) scanf("%d%d%d",&x[i],&y[i],&w[i]);
double l=0,r=10000010.0,ans=0.0;
while((r-l)>1e-6)
{
double mid=(l+r)/2;
if(check(mid)) ans=mid,l=mid;
else r=mid;
}
printf("%.3lf\n",ans);
}
return 0;
}
迭代:
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int n,pre[N],x[N],y[N],w[N];
bool st[N];
double minn[N],cost[N][N],len[N][N];
double calc(int a,int b){return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));}
double prim(double k)
{
memset(st,0,sizeof st);
st[1]=1;
for(int i=2;i<=n;i++) minn[i]=cost[1][i]-len[1][i]*k,pre[i]=1;
double Cost=0,Len=0;
for(int i=1;i<=n;i++)
{
double minnn=1e18;int now=1;
for(int j=1;j<=n;j++) if(!st[j] && minn[j] < minnn) minnn=minn[j],now=j;
st[now]=1;
Cost+=cost[pre[now]][now],Len+=len[pre[now]][now];
for(int j=1;j<=n;j++)
{
double tmp=cost[now][j]-k*len[now][j];
if(!st[j] && tmp < minn[j]) minn[j]=tmp,pre[j]=now;
}
}
return Cost/Len;
}
int main()
{
while(~scanf("%d",&n) && n)
{
for(int i=1;i<=n;i++) scanf("%d%d%d",&x[i],&y[i],&w[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) cost[i][j]=fabs(w[i]-w[j]),len[i][j]=calc(i,j);
double a,b;
while(1)
{
b=prim(a);
if(fabs(b-a) < 1e-4) break;
a=b;
}
printf("%.3lf\n",a);
}
return 0;
}
标签:分数,之王,题解,sum,mid,st,int,村庄,double 来源: https://www.cnblogs.com/EastPorridge/p/16380469.html