【POJ 2728】Desert King

Desert King

Time Limit: 3000MS   Memory Limit: 65536K
Total Submissions: 21361   Accepted: 5974

Description

David the Great has just become the king of a desert country. To win the respect of his people, he decided to build channels all over his country to bring water to every village. Villages which are connected to his capital village will be watered. As the dominate
ruler and the symbol of wisdom in the country, he needs to build the channels in a most elegant way.

After days of study, he finally figured his plan out. He wanted the average cost of each mile of the channels to be minimized. In other words, the ratio of the overall cost of the channels to the total length must be minimized. He just needs to build the necessary
channels to bring water to all the villages, which means there will be only one way to connect each village to the capital.

His engineers surveyed the country and recorded the position and altitude of each village. All the channels must go straight between two villages and be built horizontally. Since every two villages are at different altitudes, they concluded that each channel
between two villages needed a vertical water lifter, which can lift water up or let water flow down. The length of the channel is the horizontal distance between the two villages. The cost of the channel is the height of the lifter. You should notice that
each village is at a different altitude, and different channels can‘t share a lifter. Channels can intersect safely and no three villages are on the same line.

As King David‘s prime scientist and programmer, you are asked to find out the best solution to build the channels.

Input

There are several test cases. Each test case starts with a line containing a number N (2 <= N <= 1000), which is the number of villages. Each of the following N lines contains three integers, x, y and z (0 <= x, y < 10000, 0 <= z < 10000000). (x, y) is the
position of the village and z is the altitude. The first village is the capital. A test case with N = 0 ends the input, and should not be processed.

Output

For each test case, output one line containing a decimal number, which is the minimum ratio of overall cost of the channels to the total length. This number should be rounded three digits after the decimal point.

Sample Input

4
0 0 0
0 1 1
1 1 2
1 0 3
0

Sample Output

1.000

Source

Beijing 2005

01分数规划,最优比例生成树。

用两种方法:

1.二分法

二分一个答案k,

如果有sigma(Hi)/sigma(Di)<k的,即sigma(Hi)-k*sigma(Di)<0,则k要缩小。

那么我们就求出以Hi-k*Di为边权的最小生成树,看这棵树的权值和是否<0即可。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#define eps 1e-6
using namespace std;
int n,m,v[1005];
double d[1005];
struct edge
{
	double h,d;
}a[1005][1005];
struct village
{
	double x,y,h;
}vi[1005];
double Getdis(int x,int y)
{
	return sqrt((vi[x].x-vi[y].x)*(vi[x].x-vi[y].x)+(vi[x].y-vi[y].y)*(vi[x].y-vi[y].y));
}
double prim(double k)
{
	for (int i=1;i<=n;i++)
		v[i]=0,d[i]=100000000.0;
	d[1]=0.0;
	double ans=0.0;
	for (int i=1;i<=n;i++)
	{
		double minn=100000000.0;
		int x=-1;
		for (int j=1;j<=n;j++)
			if (!v[j]&&d[j]<minn)
			{
				x=j;
				minn=d[j];
			}
		v[x]=1;
		ans+=d[x];
		for (int j=1;j<=n;j++)
			if (!v[j]&&d[j]>a[x][j].h-k*a[x][j].d)
				d[j]=a[x][j].h-k*a[x][j].d;
	}
	return ans;
}
void Solve()
{
	double l=0.0,r=1000.0;
	while (r-l>eps)
	{
		double m=(l+r)/(double)2;
		if (prim(m)<0) r=m;
		else l=m;
	}
	printf("%.3f\n",l);
}
double abss(double x)
{
	if (x>0) return x;
	return -x;
}
int main()
{
        while (scanf("%d",&n)!=EOF&&n)
	{
		for (int i=1;i<=n;i++)
			scanf("%lf%lf%lf",&vi[i].x,&vi[i].y,&vi[i].h);
		for (int i=1;i<=n;i++)
			for (int j=i+1;j<=n;j++)
			{
				a[i][j].h=a[j][i].h=abss(vi[i].h-vi[j].h);
				a[i][j].d=a[j][i].d=Getdis(i,j);
			}
		Solve();
	}
	return 0;
}

一开始为什么一直WA呢?

我对sigma(Hi)/sigma(Di)<k这个式子的变法是k*sigma(Di)-sigma(Hi)>0,因为这里是>0所以要求最大生成树!

2.Dinkelbach

这个方法是先随便给出一个答案l,然后根据这个l求最小生成树得到一个比率是k,这个k既然要比l更优,那么我们直接

用k迭代下去就可以,就不需要漫无目的的二分下去了。可以证明k最终会收敛到一个值上,这个值就是答案。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#define eps 1e-6
using namespace std;
int n,m,v[1005];
double d[1005];
double aa[1005],bb[1005];
struct edge
{
	double h,d;
}a[1005][1005];
struct village
{
	double x,y,h;
}vi[1005];
double p,q;
double Getdis(int x,int y)
{
	return sqrt((vi[x].x-vi[y].x)*(vi[x].x-vi[y].x)+(vi[x].y-vi[y].y)*(vi[x].y-vi[y].y));
}
double prim(double k)
{
	for (int i=1;i<=n;i++)
		v[i]=0,d[i]=100000000.0;
	d[1]=0.0;
	p=q=0.0;
	for (int i=1;i<=n;i++)
	{
		double minn=100000000.0;
		int x=-1;
		for (int j=1;j<=n;j++)
			if (!v[j]&&d[j]<minn)
			{
				x=j;
				minn=d[j];
			}
		v[x]=1;
		p+=aa[x],q+=bb[x];
		for (int j=1;j<=n;j++)
			if (!v[j]&&d[j]>a[x][j].h-k*a[x][j].d)
				d[j]=a[x][j].h-k*a[x][j].d,aa[j]=a[x][j].h,bb[j]=a[x][j].d;
	}
	return p/q;
}
void Solve()
{
	double l=10.0;
	while (1)
	{
		double k=prim(l);
		if (fabs(k-l)<eps) break;
		l=k;
	}
	printf("%.3f\n",l);
}
double abss(double x)
{
	if (x>0) return x;
	return -x;
}
int main()
{
        while (scanf("%d",&n)!=EOF&&n)
	{
		for (int i=1;i<=n;i++)
			scanf("%lf%lf%lf",&vi[i].x,&vi[i].y,&vi[i].h);
		for (int i=1;i<=n;i++)
			for (int j=i+1;j<=n;j++)
			{
				a[i][j].h=a[j][i].h=abss(vi[i].h-vi[j].h);
				a[i][j].d=a[j][i].d=Getdis(i,j);
			}
		Solve();
	}
	return 0;
}

这个方法要快很多。

但是有的题目是不适合用此法的,比如【POJ 3621】求负环这道题,很难去记录当前的比率是多少。

感悟:

1.在进行二分判断的时候,要注意如果是<0,就要找最小的看是否<0;如果是>0就要找最大的,看是否>0

2.对于Dinkelbach的证明,可以看这里

我的大概理解是:通过当前的k值求得的新的比率要比k更优,我们一直取更优的,最终就会取到最优的了

时间: 2024-08-29 14:56:05

【POJ 2728】Desert King的相关文章

【POJ】【2728】 Desert King 最优比率生成树

题意:给出每个点的坐标(x,y,z),两点间距离是x,y的直线距离,边权为z差,求∑边权 / ∑距离 的最小值. 最优比率生成树!(分数规划) 就是根据分数规划的思想建树,每次看得到的总和是正是负. 二分代码: #include<string.h> #include<stdio.h> #include<stdlib.h> #include<math.h> #define N 1010 typedef struct KSD { int x,y,z; }ksd;

【POJ 2942】Knights of the Round Table(双联通分量+染色判奇环)

[POJ 2942]Knights of the Round Table(双联通分量+染色判奇环) Time Limit: 7000MS   Memory Limit: 65536K Total Submissions: 11661   Accepted: 3824 Description Being a knight is a very attractive career: searching for the Holy Grail, saving damsels in distress, an

【POJ 2482】 Stars in Your Window(线段树+离散化+扫描线)

[POJ 2482] Stars in Your Window(线段树+离散化+扫描线) Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 11294   Accepted: 3091 Description Fleeting time does not blur my memory of you. Can it really be 4 years since I first saw you? I still remembe

【POJ 3204】Ikki&#39;s Story I - Road Reconstruction

Ikki's Story I - Road Reconstruction Time Limit: 2000MS   Memory Limit: 131072K Total Submissions: 7089   Accepted: 2039 Description Ikki is the king of a small country – Phoenix, Phoenix is so small that there is only one city that is responsible fo

【POJ 1408】 Fishnet (叉积求面积)

[POJ 1408] Fishnet (叉积求面积) 一个1*1㎡的池塘 有2*n条线代表渔网 问这些网中围出来的最大面积 一个有效面积是相邻两行和相邻两列中间夹的四边形 Input为n 后面跟着四行 每行n个浮点数 每一行分别代表a,b,c,d 如图 并且保证a(i) > a(i-1) b(i) > b(i-1) c(i) > c(i-1) d(i) > d(i-1) n(n <= 30)*2+4(四个岸)条边 枚举点数就行 相邻的四个四个点枚举 找出围出的最大面积 找点用

【POJ 2513】Colored Sticks

[POJ 2513]Colored Sticks 并查集+字典树+欧拉通路 第一次做这么混的题..太混了-- 不过题不算难 字典树用来查字符串对应图中的点 每个单词做一个点(包括重复单词 题意就是每个边走且直走一次(欧拉通路 欧拉图的判定: 没有或者只有两个奇数度的点的图叫做欧拉图 有这些就可以解答此题了 另外需要注意题目范围是25W个木棍 所以最多可能有50W个点 卡了好多个RE 代码如下: #include <iostream> #include <cstdlib> #incl

2292: 【POJ Challenge 】永远挑战

2292: [POJ Challenge ]永远挑战 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 553  Solved: 230[Submit][Status][Discuss] Description lqp18_31和1tthinking经常出题来虐ftiasch.有一天, lqp18_31搞了一个有向图,每条边的长度都是1. 他想让ftiasch求出点1到点 N 的最短路."水题啊.", ftiasch这么说道. 所以1tth

【POJ 1201】 Intervals(差分约束系统)

[POJ 1201] Intervals(差分约束系统) 11 1716的升级版 把原本固定的边权改为不固定. Intervals Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 23817   Accepted: 9023 Description You are given n closed, integer intervals [ai, bi] and n integers c1, ..., cn. Write a p

【POJ 1228】Grandpa&#39;s Estate 凸包

找到凸包后暴力枚举边进行$check$,注意凸包是一条线(或者说两条线)的情况要输出$NO$ #include<cmath> #include<cstdio> #include<cstring> #include<algorithm> #define N 1003 #define read(x) x = getint() using namespace std; inline int getint() { int k = 0, fh = 1; char c