洛谷 P2600 [ZJOI2008]瞭望塔
洛谷 P2600 [ZJOI2008]瞭望塔
题目描述
致力于建设全国示范和谐小村庄的H村村长dadzhi,决定在村中建立一个瞭望塔,以此加强村中的治安。
我们将H村抽象为一维的轮廓。如下图所示
我们可以用一条山的上方轮廓折线(x1, y1), (x2, y2), …. (xn, yn)来描述H村的形状,这里x1 < x2 < …< xn。瞭望塔可以建造在[x1, xn]间的任意位置, 但必须满足从瞭望塔的顶端可以看到H村的任意位置。可见在不同的位置建造瞭望塔,所需要建造的高度是不同的。为了节省开支,dadzhi村长希望建造的塔高度尽可能小。
请你写一个程序,帮助dadzhi村长计算塔的最小高度。
输入格式
输入文件tower.in第一行包含一个整数n,表示轮廓折线的节点数目。接下来第一行n个整数, 为x1 ~ xn. 第三行n个整数,为y1 ~ yn。
输出格式
输出文件tower.out仅包含一个实数,为塔的最小高度,精确到小数点后三位。
题解:
首先明确,它不一定是在最高的点修塔。很容易就能构造出一种反例来hack掉这种想法。
所以直接选出建塔点之后枚举或二分长度这种想法破灭了。
所以开始考虑选点的问题。
咋选点呢?首先想到二分选点,但是这玩意并不是一个单调函数,理想破灭。
?等等,不单调,可不可以三分。
可以!!
欣喜若狂:发现如果往左建,就要花力气看看右侧的点,如果往右建,就要花力气看看左侧的点。这样的话就可以确定建塔处,至于塔高直接取所有线段反向延长后到此的最大值即可。
应该还是很容易的,对三分不太熟悉的小伙伴可以参照这里:浅谈三分法
代码:
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=310;
const double eps=1e-7;
const double INF=1e20;
int n;
double ans=INF;
struct node
{
double x,y;
}p[maxn];
struct edge
{
double k,b;
bool f;//是否平
}e[maxn];
double calc(double x,double y)
{
double tmp=0;
for(int i=1;i<n;i++)
{
if(!e[i].f)
continue;
tmp=fmax(tmp,e[i].k*x+e[i].b-y);
}
return tmp;
}
bool check(double x1,double x2,int i)
{
return calc(x1,e[i].k*x1+e[i].b)<calc(x2,e[i].k*x2+e[i].b);
}
int main()
{
// freopen("cold.in","r",stdin);
// freopen("cold.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lf",&p[i].x);
for(int i=1;i<=n;i++)
scanf("%lf",&p[i].y);
for(int i=1;i<n;i++)
{
if(fabs(p[i].x-p[i+1].x)<eps)
continue;//平
e[i].k=(p[i].y-p[i+1].y)/(p[i].x-p[i+1].x);
e[i].b=p[i].y-p[i].x*e[i].k;
e[i].f=1;
}
for(int i=1;i<n;i++)
{
if(!e[i].f)
continue;
int t=612;
double l=p[i].x;
double r=p[i+1].x;
double mid1,mid2;
while(t--)
{
mid1=l+(r-l)/3;
mid2=r-(r-l)/3;
if(check(mid1,mid2,i))
r=mid2;
else
l=mid1;
}
ans=fmin(ans,calc(mid1,e[i].k*mid1+e[i].b));
}
printf("%.3lf",ans);
return 0;
}