题解 P1663 【山】
二分+思维的一道妙题。
需要些许数学知识和一个有点空间意识的脑子。
题意简述:
平面直角坐标系上N个点相连形成一条折线。
要求找到Y值最小的一个点。
使得它位于每一段折线所在的直线的上方。
思路历程:
考虑平面直角坐标系上的整条折线如下图:
而把“每一段折线所在的直线”补起来就像这样:
\qquad \mathcal{Task\ 1.} 什么样的点才满足题目要求?
我们要取的是”每一段折线所在直线“的上方的点。
由于每一段折线的起点与终点是明确已知的,所以他的“上半部分”必然也是已知的。
而我们选取的点,则需要位于每一段折线的“上半部分”,所以便是要取所有“上半部分”的交。
看图我们便可知道,必定存在且只存在一个
这个最小的
\qquad \mathcal{Task\ 2.} 如何查一条水平直线是否包含阴影部分?
『阴影部分必然是一个联通的多边形。』
原因显然,在同一条水平直线上,不可能存在一个点,一开始在一条直线上方,突然就不在了,突然又到了他的上方的情况(这可是一次函数)。
所以对于一条水平直线,他上面的阴影部分要么没有,要有就是一个固定的区间。
既然阴影部分在一条水平直线上,只存在一个区间。那这个阴影区间我们便可以通过每一段折线的斜率信息,来限定在一个明确的范围里。
\qquad \mathcal{Task\ 3.} 怎么限定阴影部分的范围?
具体地说,限定方法如下:
-
对于一个固定的
Y ,扫一遍每一段折线所在直线,讨论:-
若该直线斜率大于 0 ,单调递增,那他的“上半部分”必然在他的左上方(这里需要一点坐标系思维)。当
Y 值固定,阴影部分必然在直线的左侧。 -
若该直线斜率等于 0,一条横着的直线。那么,如果现在的
Y 值小于直线所在Y 值,则必然不存在阴影部分。 -
若斜率小于零,单调递减,“上半部分”在 右上方 。当
Y 值固定,阴影部分必然在直线的右侧。
-
不妨画图理解。我们现在暂定下
黄线的
此时需要我们改变
此时蓝线的斜率小于0,故阴影部分的区间被限制在
此时粉线的斜率大于0,故阴影部分的区间被限制在
这时我们就有了一个明确的区间了。而一条水平直线上,阴影区间存在,也就是代表包含了阴影部分,它的条件显然是 右边界大于左边界 。
那这道题就可以轻松解决了,我们只需二分找到最小的,使阴影区间存在的
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int MAX = 5007;
const double INF = 2e9;
struct line//代表一条直线
{
double k, b;
//斜率与截距
} l[MAX];
int N;
double x[MAX], y[MAX];
//存点的 x,y 坐标
double L, R;//左、右边界
double ans;//答案(指最低的Y值)
bool judge(double Y)//获取阴影区间
{
L = -INF, R = INF;
for (int i = 1; i <= N; i++)
{
if (l[i].k < 0)//截距<0,更新左边界
{
L = max(L, (Y - l[i].b) / l[i].k);
}
if (l[i].k > 0)//截距>0,更新右边界
{
R = min(R, (Y - l[i].b) / l[i].k);
}
if (l[i].k == 0)
{
if (l[i].b > Y)//截距=0,若低于这条线,则必不存在阴影区间
{
return 0;
}
}
}
return R - L >= 1e-8;//返回右区间是否大于左区间
}
int main()
{
cin >> N;
for (int i = 1; i <= N; i++)
{
cin >> x[i] >> y[i];//输入
}
N--;
for (int i = 1; i <= N; i++)//算出来斜率与截距
{
l[i].k = (1.0) * (y[i] - y[i + 1]) / (x[i] - x[i + 1]);
l[i].b = (1.0) * y[i] - x[i] * l[i].k;
}
double l = 0, r = INF;
while (r - l >= 1e-8)//二分查找最小Y值
{
double mid = (l + r) / 2.0;
if (judge(mid))
{
ans = mid;
r = mid;
//不能是mid-1,因为Y是小数,不敢说(mid-1)~mid 之间没有答案
}
else
{
l = mid;
}
}
printf("%.2lf\n", ans);
}