【BOI2012】Mobile

Description

平面上有一条线段,从(0,0)到(L,0)
有N个点,求线段上任意一点到这N个点中最近点的距离的最大值
换言之,求一个半径,使得以这N个点为圆心所成的圆能覆盖整条线段
1<=N<=10^6,1<=L<=10^9
输入的N个点按照x坐标不下降排列。如果两个点的x坐标相同,那么它们之间按照y坐标的升序排列。

Analysis

显然时间要求线性
假设所有n个点x坐标均不相同。如果有两个点x坐标相同,那么显然只需要保留y坐标绝对值较小的一个即可。这样可以少掉一些特判。
对于两个点,其中垂线左侧的点到左边的点更近,反之亦然
按x坐标的顺序从小到大扫描n个整点,用一个栈保存可能成为最近点的整点。同时维护栈中两点连线段中垂线与给出线段交点x坐标单调递增。每当加入一个点前,判断是否破坏了栈的单调性,如果不满足单调性则弹出栈顶元素直到单调性被满足,再将新点加入栈顶。最后答案即为线段的端点以及所有中垂线交点与他们最近点的距离的最大值。
证明的话可以自己手玩,看看如果单调性不满足会怎样

Code

#include<cstdio>
#include<cmath>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef double db;
const db eps=1e-5;
const int N=1000005;
struct node
{
    db x,y;
}a[N],b[N];
int n,m,top,q[N];
db L,ans;
db sqr(db x){return x*x;}
db dis(db x1,db y1,db x2,db y2)
{
    return sqrt(sqr(x1-x2)+sqr(y1-y2));
}
db calc(node a,node b)
{
    node c;
    c.x=(a.x+b.x)/2,c.y=(a.y+b.y)/2;
    return (a.y-c.y)*c.y/(a.x-c.x)+c.x;
}
int main()
{
    freopen("mobile.in","r",stdin);
    freopen("mobile.out","w",stdout);
    db x=1e18,y=1e18;
    scanf("%d %lf",&m,&L);
    fo(i,1,m)
    {
        scanf("%lf %lf",&b[i].x,&b[i].y);
        x=min(x,dis(0,0,b[i].x,b[i].y));
        y=min(y,dis(L,0,b[i].x,b[i].y));
    }
    ans=max(x,y);
    b[m+1].x=1e18,x=b[1].y;
    fo(i,2,m+1)
        if(b[i].x!=b[i-1].x) a[++n].x=b[i-1].x,a[n].y=x,x=b[i].y;
        else
        if(abs(b[i].y)<abs(x)) x=b[i].y;
    q[top=1]=1;
    fo(i,2,n)
    {
        while(top>1 && calc(a[q[top]],a[i])<calc(a[q[top-1]],a[q[top]])) top--;
        if(top==1 && calc(a[q[top]],a[i])<0) top--;
        if(calc(a[q[top]],a[i])>L) continue;
        q[++top]=i;
    }
    while(top>1)
    {
        db pos=calc(a[q[top-1]],a[q[top]]);
        ans=max(ans,dis(pos,0,a[q[top]].x,a[q[top]].y));
        top--;
    }
    printf("%.3lf",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值