[半平面交] BZOJ1038: [ZJOI2008]瞭望塔

本文介绍了一种计算在一维轮廓上建立瞭望塔的最小高度的方法。通过求下半平面交形成凸壳,并枚举所有折点来确定最优塔高。代码使用C++实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

致力于建设全国示范和谐小村庄的H村村长dadzhi,决定在村中建立一个瞭望塔。
我们将H村抽象为一维的轮廓。
我们可以用一条山的上方轮廓折线(x1, y1), (x2, y2), …. (xn, yn)来描述,x1 < x2 < …< xn。
瞭望塔可以建造在[x1, xn]间的任意位置, 但必须满足从瞭望塔的顶端可以看到H村的任意位置。
请你写一个程序,帮助dadzhi村长计算塔的最小高度。

题解

刚开始看了半天还没看懂样例,后来才知道,我以为塔是建在x=0上的,其实是在轮廓折线上……我好智障……..
先不管塔高,考虑要看到某一线段,塔顶必须是在线段所在直线的上方。
显然求一下半平面交即可得到一个下凸的凸壳,在他上方的区域即是塔顶合法位置。
那么如何求塔高呢?
其实我们只需要每枚举在所有折点位置造塔即可,这里说的折点包括轮廓折线上的折点和凸壳上的折点。
这样一定包含最优高度,道理太显然了。

代码借鉴了Claris巨神。自己写的可能是由于精度问题一直wa。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define eps 1e-7
using namespace std;
const int maxn=305;
struct Point{ double x,y; } p[maxn],t;
struct Line{
    double k,b;
    void init(Point A,Point B) {
        k=(A.y-B.y)/(A.x-B.x);
        b=B.y-k*B.x;
    }
} L[maxn],stk[maxn];
int n,top;
double res=1e+50;
bool my_cmp(Line A,Line B){
    if(abs(A.k-B.k)<eps) return A.b<B.b;
    return A.k<B.k;
}
Point inter(Line A,Line B){
    Point res;
    res.x=(B.b-A.b)/(A.k-B.k);
    res.y=A.k*res.x+A.b;
    return res;
}
double f(double x){
    double res=0;
    for(int i=1;i<=top;i++) res=max(res,stk[i].k*x+stk[i].b);
    return res;
}
double g(double x){
    int i; for(i=n;i&&x<p[i].x;i--);
    return p[i].y+(x-p[i].x)/(p[i+1].x-p[i].x)*(p[i+1].y-p[i].y);    
}
int main() {
    freopen("bzoj1038.in","r",stdin);
    freopen("bzoj1038.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-1;i++) L[i].init(p[i],p[i+1]);
    sort(L+1,L+n,my_cmp);
    for(int i=1;i<=n-1;i++) if(i==n-1||abs(L[i].k-L[i+1].k)>eps){
        while(top>=2&&inter(stk[top],L[i]).x<inter(stk[top-1],stk[top]).x) top--;    
        stk[++top]=L[i];
    }
    for(int i=1;i<=n;i++) res=min(res,f(p[i].x)-p[i].y);
    for(int i=1;i<=top-1;i++) {
        Point now=inter(stk[i],stk[i+1]);
        res=min(res,now.y-g(now.x));
    }
    printf("%.3lf\n",res);
    return 0;
}   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值