其他分享
首页 > 其他分享> > 0/1分数规划 生成树 沙漠之王 题解

0/1分数规划 生成树 沙漠之王 题解

作者:互联网

Acwing348. 沙漠之王

题目描述

大卫大帝刚刚建立了一个沙漠帝国,为了赢得他的人民的尊重,他决定在全国各地建立渠道,为每个村庄提供水源。

与首都相连的村庄将得到水资源的浇灌。

他希望构建的渠道可以实现单位长度的平均成本降至最低。

换句话说,渠道的总成本和总长度的比值能够达到最小。

他只希望建立必要的渠道,为所有的村庄提供水资源,这意味着每个村庄都有且仅有一条路径连接至首都。

他的工程师对所有村庄的地理位置和高度都做了调查,发现所有渠道必须直接在两个村庄之间水平建造。

由于任意两个村庄的高度均不同,所以每个渠道都需要安装一个垂直的升降机,从而使得水能够上升或下降。

建设渠道的成本只跟升降机的高度有关,换句话说只和渠道连接的两个村庄的高度差有关。

需注意,所有村庄(包括首都)的高度都不同,不同渠道之间不能共享升降机。

输入格式

输入包含多组测试数据。

输入

每组测试数据第一行包含整数 \(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