这是我人生中第一道独立写出来的蓝题,今天突然想写一篇题解
题意
选出一个点,使得它与这座“山”上的每一个点的连线都与这座“山”没有交点
解法1:暴力
解题第一步,考虑暴力
暴力怎么写呢?考虑枚举 y y y ,因为题目要求输出与答案的差不超过 0.01 ,所以我们把精度设为 0.01 ,这样暴力:
double y=0;
while(不符合条件)
y+=0.01
输出y
那么剩下的问题就是怎么判断是否符合条件,我们可以先从下面这张图入手:
很明显, y = 4 y=4 y=4 时,蓝色部分的 x x x 是合法的,也就是对于下降的部分( 斜率 < 0 时 \text{斜率}<0\ \text{时} 斜率<0 时 ), x ≥ ( f ( x ) = y 中 x 的解 ) x \ge (f(x)=y\text{ 中 }x\text{ 的解}) x≥(f(x)=y 中 x 的解) ,我们设 f ( x ) = a x + b f(x)=ax+b f(x)=ax+b ,解 a x + b = y ax+b=y ax+b=y ,解得 x = y − b a x=\frac{y-b}{a} x=ay−b ,所以当 a < 0 a<0 a<0 时, x ≥ y − b a x \ge \frac{y-b}{a} x≥ay−b
类似的,我们看
a
>
0
a>0
a>0 的情况:
蓝色部分合法,那么 x ≤ y − b a x \le \frac{y-b}{a} x≤ay−b
特殊的,对于 a = 0 a=0 a=0 的情况,当 y < a y<a y<a , x x x 无解,当 y ≥ a y\ge a y≥a , x x x 取任意实数
那么我们就可以求出任意一段“山坡”,在
y
y
y 确定的情况下
x
x
x 的范围,我们对于每一段山坡
x
x
x 的取值范围,我们对于这些范围取交集,如果最后的集合不是空集,那么说明这个
y
y
y 可行,首先我们需要把这些点转换成解析式,我们假设有两个点
(
x
1
,
y
1
)
,
(
x
2
,
y
2
)
(x_1,y_1),(x_2,y_2)
(x1,y1),(x2,y2) ,这条线满足
y
=
a
x
+
b
y=ax+b
y=ax+b ,那么:
{
a
x
1
+
b
=
y
1
a
x
2
+
b
=
y
2
\left\{\begin{matrix} ax_1+b=y_1 & \\ ax_2+b=y_2 & \end{matrix}\right.
{ax1+b=y1ax2+b=y2
解得:
{
a
=
y
1
−
y
2
x
1
−
x
2
b
=
y
1
−
a
x
1
\left\{\begin{matrix} a=\frac{y_1-y_2}{x_1-x_2} & \\ b=y_1-ax_1 & \end{matrix}\right.
{a=x1−x2y1−y2b=y1−ax1
献上暴力代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int NR=5010;
int n;
struct ttt
{
double a, b;
}a[NR], b[NR];
//a存输入,b存ax+b中的a,b
bool chk(double y)
{
double l=-2e9, r=2e9;
for(int i=1;i<n;i++)
if(b[i].a>0)r=min(r, (y-b[i].b)/b[i].a);//a>0的情况
else if(b[i].a<0)l=max(l, (y-b[i].b)/b[i].a);//a<0的情况
else if(y<b[i].b)return false;//a=0并且y<b的情况
return l<=r;
}
int main()
{
scanf("%d", &n);
for(int i=1;i<=n;i++)
scanf("%lf%lf", &a[i].a, &a[i].b);
for(int i=1;i<n;i++)
{//转换
b[i].a=(a[i].b-a[i+1].b)/(a[i].a-a[i+1].a);
b[i].b=a[i].b-b[i].a*a[i].a;
}
double ans=0;
while(!chk(ans))ans+=0.01;//枚举
printf("%.2lf", ans);
return 0;
}
然后你会发现你得了 40 分
正解:二分
我们可以发现上面的代码中枚举的部分,我们是要找到最高的满足条件的 y y y ,而且越高越满足条件,考虑二分:
double l=0, r=1e6, m, ans;
while(l<=r)
{
m=(l+r)/2;
if(chk(m))ans=m, r=m-PR;
else l=m+PR;
}
printf("%.2lf", ans);
其中 P R PR PR 是精度,这里 0.01 就够,这里 r r r 的值也不是随便写的,题目中说 y i ≤ 1 0 6 y_i \le 10^6 yi≤106 ,所以答案不可能超过 1 e 6 1e6 1e6 ,献上完整代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int NR=5010;
const double PR=1e-2;
int n;
struct ttt
{
double a, b;
}a[NR], b[NR];
bool chk(double y)
{
double l=-2e9, r=2e9;
for(int i=1;i<n;i++)
if(b[i].a>0)r=min(r, (y-b[i].b)/b[i].a);
else if(b[i].a<0)l=max(l, (y-b[i].b)/b[i].a);
else if(y<b[i].b)return false;
return l<=r;
}
int main()
{
scanf("%d", &n);
for(int i=1;i<=n;i++)
scanf("%lf%lf", &a[i].a, &a[i].b);
for(int i=1;i<n;i++)
{
b[i].a=(a[i].b-a[i+1].b)/(a[i].a-a[i+1].a);
b[i].b=a[i].b-b[i].a*a[i].a;
}
double l=0, r=1e6, m, ans;
while(l<=r)
{
m=(l+r)/2;
if(chk(m))ans=m, r=m-PR;
else l=m+PR;
}
printf("%.2lf", ans);
return 0;
}
优化
虽然上述做法已经能 100 分了,但是其实我们还可以再优化,比如把 d o u b l e double double 换成整型,这样还能再快一点,代码懒得写了,各位自己尝试吧