最优比率生成树(0/1分数规划+二分+最小生成树)

题目描述

大卫大帝刚刚成为一个沙漠国家的国王。为了赢得人民的尊重,他决定在全国各地修建渠道,为每个村庄供水。与他的首都村庄相连的村庄将被浇灌。作为国家的主宰者和智慧的象征,他需要以最优雅的方式构建通道。

经过几天的研究,他终于想出了自己的计划。他希望将每英里通道的平均成本降至最低。换句话说,通道的总成本与总长度的比率必须最小化。他只需要建立必要的渠道,将水输送到所有村庄,这意味着将每个村庄连接到首都只有一种方法。

他的工程师们勘察了整个国家并记录了每个村庄的位置和高度。所有通道必须直通两个村庄之间,并水平建造。由于每两个村庄的海拔高度不同,他们得出结论,两个村庄之间的每个通道都需要一个垂直的提水器,可以提水或让水流下来。通道的长度是两个村庄之间的水平距离。通道的成本是升降机的高度。你应该注意到,每个村庄在不同的高度,不同的频道不能共用一个升降机。渠道可以安全相交,没有三个村庄在同一条线上。

作为大卫王的首席科学家和程序员,您需要找出构建渠道的最佳解决方案。

题意简介

给出 n个点的坐标 和海拔,任意两点连得边的长度为这两个点的直线距离(勾股定理),边的花费为海拔之差,希望找一棵生成树,并最小化
∑ i = 1 n − 1 c o s t [ i ] ∑ i = 1 n − 1 d i s [ i ] \frac{\sum_{i=1}^{n-1}{cost[i]}}{\sum_{i=1}^{n-1}{dis[i]}} i=1n1dis[i]i=1n1cost[i]
其中 i i i 表示生成树每一条边, c o s t [ i ] cost[i] cost[i]表示第 i i i 条边的花费, d i s [ i ] dis[i] dis[i] 表示第 i i i 条边的长度

解题思路

考虑 0/1分数规划
因为本蒟蒻不会Dinkelbach,所以只能写二分
实数二分一个答案 w w w ,看是否能有一种生成树,使
∑ i = 1 n − 1 c o s t [ i ] ∑ i = 1 n − 1 d i s [ i ] ≤ w \frac{\sum_{i=1}^{n-1}{cost[i]}}{\sum_{i=1}^{n-1}{dis[i]}}\leq{w} i=1n1dis[i]i=1n1cost[i]w
∑ i = 1 n − 1 c o s t [ i ] ≤ w × ∑ i = 1 n − 1 d i s [ i ] \sum_{i=1}^{n-1}{cost[i]}\leq{w}\times\sum_{i=1}^{n-1}{dis[i]} i=1n1cost[i]w×i=1n1dis[i]
∑ i = 1 n − 1 c o s t [ i ] − w × ∑ i = 1 n − 1 d i s [ i ] ≤ 0 \sum_{i=1}^{n-1}{cost[i]}-{w}\times\sum_{i=1}^{n-1}{dis[i]}\leq{0} i=1n1cost[i]w×i=1n1dis[i]0
也就是把每一条边变成 c o s t [ i ] − w × d i s [ i ] cost[i]-{w}\times{dis[i]} cost[i]w×dis[i]
然后求出最小生成树,如果边权和小于0,就调小w,否则就调大
PS:因为任意两点都可以连边,因此这是一个完全图,所以还是 P r i m Prim Prim跑的快一点
代码如下

#include<bits/stdc++.h>
using namespace std;
const int N=1005;
double cost[N][N];
double len[N][N];
double dis[N];
int x[N],y[N],h[N];
bool vis[N];
int 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]));
}
inline int read()
{
	int X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
	while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}
bool check(double w)
{
	double sum=0.000;
	for(int i=1;i<=n;i++)
	{
		dis[i]=cost[1][i]-len[1][i]*w;	
	}

	memset(vis,0,sizeof(vis));
	vis[1]=1;
	for(int t=2;t<=n;t++)
	{
		double minx=1e9+7;
		int pos=-1;
		for(int i=1;i<=n;i++)
		{
			if(vis[i]) continue;
			if(dis[i]<minx)
			{
				minx=dis[i];
				pos=i;
			}
		}
		vis[pos]=1;
		sum+=minx;
		for(int i=1;i<=n;i++)
		{
			if(vis[i]) continue;
			if(dis[i]>cost[pos][i]-len[pos][i]*w)
			{
				dis[i]=cost[pos][i]-len[pos][i]*w;
			}
		}
	}
	if(sum-0<0.000) return 1;
	return 0;
}
int main()
{
	freopen("king.in","r",stdin);
	freopen("king.out","w",stdout);
	while(n=read())
	{			
		for(int i=1;i<=n;i++)
		{
			x[i]=read();
			y[i]=read();
			h[i]=read();
		}
		for(int i=1;i<=n;i++)
		{
			for(int j=i+1;j<=n;j++)
			{
				len[j][i]=len[i][j]=calc(i,j);
				cost[j][i]=cost[i][j]=fabs(h[i]-h[j]);
			}
		}
		double l=0.000,r=100.000,mid,ans;
		while(r-l>1e-6)
		{
			mid=(l+r)/2;
			if(check(mid))
			{
				ans=mid;
				r=mid;
			}
			else l=mid;
		}
	printf("%.3lf\n",ans);	
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值