编程之美初赛第一场 题目3 : 活动中心
题目3 : 活动中心
描述
A市是一个高度规划的城市,但是科技高端发达的地方,居民们也不能忘记运动和锻炼,因此城市规划局在设计A市的时候也要考虑为居民们建造一个活动中心,方便居住在A市的居民们能随时开展运动,锻炼强健的身心。
城市规划局希望活动中心的位置满足以下条件:
1. 到所有居住地的总距离最小。
2. 为了方便活动中心的资源补给和其他器材的维护,活动中心必须建设在A市的主干道上。
为了简化问题,我们将A市摆在二维平面上,城市的主干道看作直角坐标系平的X轴,城市中所有的居住地都可以看成二维平面上的一个点。
现在,A市的城市规划局希望知道活动中心建在哪儿最好。
输入
第一行包括一个数T,表示数据的组数。
接下来包含T组数据,每组数据的第一行包括一个整数N,表示A市共有N处居住地
接下来N行表示每处居住地的坐标。
输出
对于每组数据,输出一行“Case X: Y”,其中X表示每组数据的编号(从1开始),Y表示活动中心的最优建造位置。我们建议你的输出保留Y到小数点后6位或以上,任何与标准答案的绝对误差或者相对误差在10-6以内的结果都将被视为正确。
数据范围
小数据:1 ≤ T ≤ 1000, 1 ≤ N ≤ 10
大数据:1 ≤ T ≤ 10, 1 ≤ N ≤ 105
对于所有数据,坐标值都是整数且绝对值都不超过106
样例解释
样例1:活动中心的最优建造位置为(1.678787, 0)
- 样例输入
-
1 3 1 1 2 2 3 3
- 样例输出
-
Case 1: 1.678787
三分法——求解凸性函数的极值问题
二分法作为分治中最常见的方法,适用于单调函数,逼近求解某点的值。但当函数是凸性函数时,二分法就无法适用,这时三分法就可以“大显身手”~~
如图,类似二分的定义Left和Right,mid = (Left + Right) / 2,midmid = (mid + Right) / 2; 如果mid靠近极值点,则Right = midmid;否则(即midmid靠近极值点),则Left = mid;
程序模版如下:double Calc(Type a) { /* 根据题目的意思计算 */ } void Solve(void) { double Left, Right; double mid, midmid; double mid_value, midmid_value; Left = MIN; Right = MAX; while (Left + EPS < Right) { mid = (Left + Right) / 2; midmid = (mid + Right) / 2; mid_area = Calc(mid); midmid_area = Calc(midmid); // 假设求解最大极值. if (mid_area >= midmid_area) Right = midmid; else Left = mid; } }
现根据几道的OJ题目来分析三分法的具体实现。
buaa 1033 Easy Problem
http://acm.buaa.edu.cn/oj/problem_show.php?c=0&p=1033
题意为在一条线段上找到一点,与给定的P点距离最小。很明显的凸性函数,用三分法来解决。
Calc函数即为求某点到P点的距离。ZOJ 3203 Light Bulb
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3203如图,人左右走动,求影子L的最长长度。
根据图,很容易发现当灯,人的头部和墙角成一条直线时(假设此时人站在A点),此时的长度是影子全在地上的最长长度。当人再向右走时,影子开始投影到墙上,当人贴着墙,影子长度即为人的高度。所以当人从A点走到墙,函数是先递增再递减,为凸性函数,所以我们可以用三分法来求解。
下面只给出Calc函数,其他直接套模版即可。double Calc(double x) { return (h * D - H * x) / (D - x) + x; }
heru 5081 Turn the corner 08年哈尔滨regional网络赛
http://acm.hrbeu.edu.cn/index.php?act=problem&proid=5081汽车拐弯问题,给定X, Y, l, d判断是否能够拐弯。首先当X或者Y小于d,那么一定不能。
其次我们发现随着角度θ的增大,最大高度h先增长后减小,即为凸性函数,可以用三分法来求解。
这里的Calc函数需要比较繁琐的推倒公式:
s = l * cos(θ) + w * sin(θ) - x;
h = s * tan(θ) + w * cos(θ);
其中s为汽车最右边的点离拐角的水平距离, h为里拐点最高的距离, θ范围从0到90。POJ 3301 Texas Trip
http://acm.pku.edu.cn/JudgeOnline/problem?id=3301
题意为给定n(n <= 30)个点,求出饱含这些点的面积最小的正方形。
有两种解法,一种为逼近法,就是每次m分角度,求出最符合的角度,再继续m分,如此进行times次,即可求出较为精确的解。(m 大概取10, times取30即可)
第二种解法即为三分法,首先旋转的角度只要在0到180度即可,超过180度跟前面的相同的。坐标轴旋转后,坐标变换为:
X’ = x * cosa - y * sina;
y’ = y * cosa + x * sina;
至于这题的函数是否是凸性的,为什么是凸性的,我也无法给出准确的证明,希望哪位路过的大牛指点一下~~对于求解一些实际问题,当公式难以递推.
本题AC代码:
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#include<cmath>
#include<string>
#include<vector>
#include<queue>
using namespace std;
double x[111111],y[111111];
int n;
double mi,ma;
double dis(double xx,int k)
{
xx=xx-x[k];
return sqrt(xx*xx+y[k]*y[k]);
}
int main()
{
int t;
int ca=1;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
mi=0;
ma=0;
for(int i=0;i<n;i++)
{
scanf("%lf%lf",&x[i],&y[i]);
mi=min(mi,x[i]);
ma=max(ma,x[i]);
}
double low=mi,high=ma,mid,mmd;
double l1,l2;
while(high-low>1e-8)
{
mid=(low+high)/2;
mmd=(mid+low)/2;
l1=l2=0;
for(int i=0;i<n;i++)
{
l1+=dis(mid,i);
l2+=dis(mmd,i);
}
if(l1>l2)
{
high=mid;
}
else low=mmd;
}
printf("Case %d: %.6f\n",ca++,low);
}
return 0;
}