01分数规划的两道例题

POJ 2728 Desert King 最优比率生成树

title

POJ 2728
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

wrong reason

使用printf不论是进行double输出还是float输出都要用%.3f。
查了半天,终于找到了原因:POJ就是个牛laji玩意儿。

问:有人告诉我不能在printf中使用%lf。为什么printf()用%f输出double型,而scanf却用%lf呢?

答:printf的%f说明符的确既可以输出float型又可以输出double型。根据“默认参数提升”规则(在printf这样的函数的可变参数列表中,不论作用域内有没有原型,都适用这一规则)float型会被提升为double型。因此printf()只会看到双精度数。参见问题15.2。

(严格地讲,%lf在printf下是未定义的,但是很多系统可能会接受它。要确保可移植性,就要坚持使用%f。)
转自qu317058542_scu

analysis

好了,言归正传,开始分析一下这道题。
首先,解释一下,欧几里得距离是个什么东西:在数学中,欧几里得距离或欧几里得度量是欧几里得空间中两点间“普通”(即直线)距离。——摘自某度。

其实原题就是求 M I N ( Σ C i X i Σ D i X i ) , X i ∈ [ 0 , 1 ] MIN(\dfrac {\Sigma _{CiXi}}{\Sigma _{DiXi}}),Xi∈[0,1] MIN(ΣDiXiΣCiXi)Xi[01] ,对每个生成树,设其比率 r = Σ C i X i Σ D i X i r=\dfrac {\Sigma _{CiXi}}{\Sigma _{DiXi}} r=ΣDiXiΣCiXi,可得 Σ C i X i − Σ D i X i ∗ r = 0 ( 条 件 1 ) \Sigma _{CiXi} - \Sigma _{DiXi}*r=0(条件1) ΣCiXiΣDiXir=01

那么对于所有的生成树,显然 Σ C i X i − Σ D i X i ∗ m i n ( r ) > = 0 \Sigma _{CiXi} - \Sigma _{DiXi}* min(r) >= 0 ΣCiXiΣDiXimin(r)>=0,当 Σ C i X i Σ D i X i = m i n ( r ) \dfrac {\Sigma _{CiXi}}{\Sigma _{DiXi}} = min(r) ΣDiXiΣCiXi=min(r)时,等号成立。
而我们现在不知道 m i n ( r ) min(r) min(r)是多少,只好进行枚举,对每个枚举的r ,构建新的权值( C i − D i ∗ r Ci-Di*r CiDir),然后求最小生成树, 为什么求最小呢?
我的理解就是这是为了寻找使得生成树的总权值为0的可能性,因为只有当其等于0 的时候,才满足了条件1 这个条件, 说明这个 r r r是可行的,并且如果 r r r枚举到值为 m i n ( r ) min(r) min(r)时,其最小生成树的的总权值必然恰好等于0,但是如果不能等于0, 比如大于0, 显然是对该r值,所有的生成树上无论如何也满足不了条件1,说明 r r r值就是偏小了。同理如果小于0, r r r值是偏大的,说明可能存在某些生成树使得满足条件1,而我们的目标是在满足条件1的情况下使得 r r r最小,摘自sdj。

code

#include
using namespace std;
const int maxn=1006,inf=0x3f3f3f3f;
const double eps=1e-6;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1, ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int n;
struct mst
{
	int x,y,z;
}e[maxn];
double a[maxn][maxn],b[maxn][maxn],c[maxn][maxn],d[maxn];
bool v[maxn];
inline double s(int i,int j)
{
	return sqrt((e[i].x-e[j].x) * (e[i].x-e[j].x) + (e[i].y-e[j].y) * (e[i].y-e[j].y));
}
inline double check(double k)
{
	for (int i=1;i<=n;++i)
		for (int j=i;j<=n;++j)
			if (i==j) c[i][j]=inf;
			else c[i][j]=c[j][i]=a[i][j]-k*b[i][j];
	memset(v,0,sizeof(v));
	for (int i=1;i<=n;++i)
		d[i]=inf;
	d[1]=0;
	double ans=0;
	while (1)
	{
		int x=0;
		for (int i=1;i<=n;++i)
			if (!v[i] && (!x || d[x]>d[i]))
				x=i;
		if (!x) break;
		v[x]=1;
		ans+=d[x];
		for (int i=1;i<=n;++i)
			d[i]=min(d[i],c[x][i]);
	}
	return ans;
}
inline void Desert_King()
{
	for (int i=1;i<=n;++i)
		read(e[i].x),read(e[i].y),read(e[i].z);
	double num=0;
	for (int i=1;i<=n;++i)
		for (int j=1;j<=n;++j)
		{
			num+=(a[i][j]=a[j][i]=abs(e[i].z-e[j].z));
			b[i][j]=b[j][i]=s(i,j);
		}
	double l=0,r=num;
	while (l+eps<=r)
	{
		double mid=(l+r)/2;
		if (check(mid)>=0) l=mid;
		else r=mid;
	}
	printf("%.3f\n",l);
}
int main()
{
	while (cin>>n && n) Desert_King();
	return 0;
}

「BZOJ1690」[Usaco2007 Dec] 奶牛的旅行 最优比率环

titlep1938

BZOJ 1690
POJ 3621
描述 Description

作为对奶牛们辛勤工作的回报,Farmer John决定带她们去附近的大城市玩一天。旅行的前夜,奶牛们在兴奋地讨论如何最好地享受这难得的闲暇。
  很幸运地,奶牛们找到了一张详细的城市地图,上面标注了城市中所有L(2 <= L <= 1000)座标志性建筑物(建筑物按1…L顺次编号),以及连接这些建筑物的P(2 <= P <= 5000)条道路。按照计划,那天早上Farmer John会开车将奶牛们送到某个她们指定的建筑物旁边,等奶牛们完成她们的整个旅行并回到出发点后,将她们接回农场。由于大城市中总是寸土寸金,有的道路都很窄,政府不得不把它们都设定为通行方向固定的单行道。
  尽管参观那些标志性建筑物的确很有意思,但如果你认为奶牛们同样享受穿行于大城市的车流中的话,你就大错特错了。与参观景点相反,奶牛们把走路定义为无趣且令她们厌烦的活动。对于编号为i的标志性建筑物,奶牛们清楚地知道参观它能给自己带来的乐趣值F_i (1 <= F_i <= 1000)。相对于奶牛们在走路上花的时间,她们参观建筑物的耗时可以忽略不计。
  奶牛们同样仔细地研究过城市中的道路。她们知道第i条道路两端的建筑物L1_i和L2_i(道路方向为L1_i -> L2_i),以及她们从道路的一头走到另一头所需要的时间T_i(1 <= T_i <= 1000)。
  为了最好地享受她们的休息日,奶牛们希望她们在一整天中平均每单位时间内获得的乐趣值最大。当然咯,奶牛们不会愿意把同一个建筑物参观两遍,也就是说,虽然她们可以两次经过同一个建筑物,但她们的乐趣值只会增加一次。顺便说一句,为了让奶牛们得到一些锻炼Farmer John要求奶牛们参观至少2个建筑物。
  请你写个程序,帮奶牛们计算一下她们能得到的最大平均乐趣值。

输入格式 Input Format

第1行: 2个用空格隔开的整数:L 和 P
第2…L+1行: 第i+1行仅有1个整数:F_i
第L+2…L+P+1行: 第L+i+1行用3个用空格隔开的整数:L1_i,L2_i以及T_i, 描述了第i条道路。

输出格式 Output Format

输出1个实数,保留到小数点后2位(直接输出,不要做任何特殊的取整操作),表示如果奶牛按题目中描述的一系列规则来安排她们的旅行的话,她们能获得的最大平均乐趣值

样例输入 Sample Input

5 7
30
10
10
5
10
1 2 3
2 3 2
3 4 5
3 5 2
4 5 5
5 1 3
5 2 2

样例输出 Sample Output

6.00
输出说明:
如果奶牛选择1 -> 2 -> 3 -> 5 -> 1的旅行路线,她们能得到的总乐趣值为60,为此她们得花费10单位的时间在走路上。于是她们在这次旅行中的平均乐趣值为6。如果她们走2 -> 3 -> 5 -> 2的路线,就只能得到30/6 = 5的平均乐趣值。并且,任何去参观建筑物4的旅行路线的平均乐趣值都没有超过4。

时间限制 Time Limitation

1s

来源 Source

usaco 2007 dec gold sightsee

wrong reason

spfa函数的传参类型错误的设成了int,害我一直WA。

analysis

首先要证明的是奶牛最后选到一定是一个简单环,如下,
假设最优解不是简单环,则其中必定有一个重复点,设为c1,对于这个点隔开到也是两个环,我们设两个环中除了这个点其他点权值和分别为,c2,c3;边权值为 a1,s2;
由于它是最优解所以有 :
1. ( c 1 + c 2 + c 3 ) / ( a 1 + a 2 ) > ( c 1 + c 2 ) / a 1 1.(c1+c2+c3)/(a1+a2) > (c1+c2)/a1 1.(c1+c2+c3)/(a1+a2)>(c1+c2)/a1
2. ( c 1 + c 2 + c 3 ) / ( a 1 + a 2 ) > ( c 2 + c 3 ) / a 2 2.(c1+c2+c3)/(a1+a2) > (c2+c3)/a2 2.(c1+c2+c3)/(a1+a2)>(c2+c3)/a2
由 1 有 : a 1 ∗ c 3 > a 2 ∗ ( c 1 + c 2 ) 由1有:a1*c3 > a2*(c1+c2) 1a1c3>a2(c1+c2)
由 2 有 : a 2 ∗ c 1 > a 1 ∗ ( c 2 + c 3 ) 由2有:a2*c1 > a1*(c2+c3) 2a2c1>a1(c2+c3)
所 以 : a 1 ∗ c 3 > a 2 ∗ c 2 + a 1 ∗ ( c 2 + c 3 ) 所以:a1*c3 > a2*c2+a1*(c2+c3) a1c3>a2c2+a1(c2+c3)
显然错误,至此可以有结论,最优解必定是简单环

基于这个结论在思考,发现如果判断某个解是否行,我们考虑的是简单环!!因为如果存在这样一个简单环则显然成立,不存在则必定不可行;所以我们可以把边权值变成, mid*t-F[to],如果存在负权环则mid是可行解。

综上发现2分答案+判负环是正确解法,摘自lzqxh

code

/*******************************************************
	Problem: 3621		User: sjh2021
	Memory: 708K		Time: 532MS
	Language: G++		Result: Accepted
********************************************************/

#include
using namespace std;
const int maxn=1001,maxm=5005;
const double eps=1e-3;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch) && ch^'-')  ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int n,m,happy[maxn];
int ver[maxm],edge[maxm],Next[maxm],head[maxn],len;
inline void add(int x,int y,int z)
{
	ver[++len]=y,edge[len]=z,Next[len]=head[x],head[x]=len;
}
double dist[maxn];
int v[maxn],cnt[maxn];
inline bool spfa(double k)
{
	memset(cnt,0,sizeof(cnt));
	memset(v,0,sizeof(v));
	for (int i=1;i<=n;++i)
		dist[i]=1e15;
	queue<int>q;
	dist[1]=0,v[1]=1;
	q.push(1),++cnt[1];
	while (!q.empty())
	{
		int x=q.front();
		q.pop();
		v[x]=0;
		for (int i=head[x];i;i=Next[i])
		{
			int y=ver[i],z=edge[i];
			if (dist[y]>dist[x]+k*z-happy[x])
			{
				dist[y]=dist[x]+k*z-happy[x];
				if (!v[y])
				{
					q.push(y),v[y]=1;
					if (++cnt[y]>n) return true;
				}
			}
		}
	}
	return false;
}
int main()
{
	read(n);read(m);
	for (int i=1;i<=n;++i)
		read(happy[i]);
	for (int i=1;i<=m;++i)
	{
		int x,y,z;
		read(x);read(y);read(z);
		add(x,y,z);
	}
	double l=0,r=10000;
	while (l+eps<r)
	{
		double mid=(l+r)/2;
		if (spfa(mid)) l=mid;
		else r=mid;
	}
	printf("%.2f\n",l);
	return 0;
}

放一个以前写的1000MS过得代码。码风有点不一样

/******************************************
	Problem: 3621		User: sjh2021
	Memory: 1308K		Time: 1000MS
	Language: G++		Result: Accepted
*******************************************/

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define _ 100010
using namespace std;
const double eps=1e-8;
struct rec
{
	int x,y,z;
}e[_];
inline int read()
{
	int f=1,num=0;
	char ch=getchar();
	while (ch<'0'||ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
	while (ch>='0'&&ch<='9') num=(num<<1)+(num<<3)+ch-'0',ch=getchar();
	return num*f;
}
int n,m,happy[_];
int ver[_],Next[_],head[_],len;
double edge[_],l,r;
void add(int x,int y,double z)
{
	ver[++len]=y,edge[len]=z,Next[len]=head[x],head[x]=len;
}
double dist[_];
bool v[_];
int cnt[_];
queue<int>q;
inline bool spfa()
{
	for (int i=1;i<=n;++i)	dist[i]=-1e9;
	memset(cnt,0,sizeof(cnt));
	memset(v,0,sizeof(v));
	dist[1]=0;
	q.push(1);
	while (!q.empty())
	{
		int x=q.front();
		q.pop();
		v[x]=0;
		for (int i=head[x];i;i=Next[i])
		{
			int y=ver[i];
			double z=edge[i];
			if (dist[y]+eps<dist[x]+z)
			{
				dist[y]=dist[x]+z;
				if (!v[y])
				{
					q.push(y),v[y]=1;
					if (++cnt[y]>n) return true;
				}
			}
		}
	}
	return false;
}
inline bool check(double v)
{
	memset(head+1,0,sizeof(int)*n);
	len=0;
	for (int i=1;i<=m;++i)
		add(e[i].x,e[i].y,happy[e[i].x]-e[i].z*v);
	if (spfa()) return true;
	else return false;
}
int main()
{
	n=read(),m=read();
	for (int i=1;i<=n;++i)
		happy[i]=read();
	for (int i=1;i<=m;++i)
	{
		int x=read(),y=read(),z=read();
		e[i]=(rec){x,y,z};
		r+=z;
	}
	while (l+eps<r)
	{
		double mid=(l+r)/2.0;
		if (check(mid)) l=mid;
		else r=mid;
	}
	printf("%.2f",l);
	return 0;
}

你可能感兴趣的:(01分数规划的两道例题)