离大海最远点在哪里?-半平面交&堆&双向链表

传送门:hzwer


题解

二分+半平面交比较显然,但常数大且卡精度。

考虑二分求解的本质:

随距离增加不断将所有边向内移,在凸包面积恰好为0时的距离就是答案。
在不断内移的过程中,一些边对凸包的限制会被其相邻两条边取代(等价于二分半平面交时这条边不在双端队列中)。于是求出每条边被取代的时刻 t i t_i ti压入堆,每次取 m i n ( t i ) min(t_i) min(ti)弹出,更改其相邻两条边的信息(双向链表维护)。直到最后只剩两条边。

t i t_i ti的求法:

  • 相邻两条边平行, t i = t_i= ti=相邻两条边距离的一半
  • 相邻两条边不平行, t i = t_i= ti=相邻两角的角平分线交点到当前边的距离‘

注意:

  1. 特判角平分线平行的情况( + ∞ +\infty +)
  2. 若弹出边的相邻边平行,则凸包面积必然已经为零,直接输出答案

代码

直接贴黄学长的了qwq

#include<queue>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define abs(x) ((x)>0?(x):-(x))
#define eps 1e-13
#define LL long long
#define LB long double
#define pa pair<LB,int>
#define mp(x,y) make_pair(x,y)
using namespace std;
LL read()
{
    LL x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n;
int pre[200005],nxt[200005];
bool del[200005];
LB ans;
struct P{
    LB x,y;
    friend LB operator*(P a,P b){
        return a.x*b.y-a.y*b.x;
    }
    friend P operator+(P a,P b){
        return (P){a.x+b.x,a.y+b.y};
    }
    friend P operator-(P a,P b){
        return (P){a.x-b.x,a.y-b.y};
    }
    friend P operator*(P a,LB b){
        return (P){a.x*b,a.y*b};
    }
    friend LB dis(P a){
        return sqrt(a.x*a.x+a.y*a.y);
    }
}p[200005];
void print(P a)
{
    printf("(%.3Lf,%.3Lf)",a.x,a.y);
}
struct L{
    P a,b;
    friend P cp(L a,L b){// crossover point
        double k1,k2,t;
		k1=(a.a-b.b)*(b.a-b.b),k2=(b.a-b.b)*(a.b-b.b);
		t=k1/(k1+k2);
		return (P){a.a.x+(a.b.x-a.a.x)*t,a.a.y+(a.b.y-a.a.y)*t};
    }
}l[200005];
L ab(P a,P b,P c){ // angle a,b,c // angular bisector
    LB l1=dis(a-b),l2=dis(b-c);
    a=b+(a-b)*(l2/l1);
    P t=a+c;t.x/=2;t.y/=2;
    return (L){b,t};        
}
LB dis(L l,P a){
    return abs((l.b-l.a)*(a-l.a))/dis(l.a-l.b);
}
priority_queue<pa,vector<pa>,greater<pa> >pq;
void add(int x)
{
    L ll=l[pre[x]],rl=l[nxt[x]];
    LB t;
    if(fabs((ll.b-ll.a)*(rl.b-rl.a))<eps)
        t=dis(ll,rl.b)/2;
    else 
    {
        L l1=ab(ll.a,ll.b,l[x].b);
        L l2=ab(l[x].a,rl.a,rl.b);
        if(fabs((l1.b-l1.a)*(l2.b-l2.a))<eps)t=1e20;
        else 
        {
            P p=cp(l1,l2);t=dis(l[x],p);
        }
    }
    pq.push(mp(t,x));
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++)
        p[i].x=read(),p[i].y=read();
    p[n+1]=p[1];
    for(int i=1;i<=n;i++)
        l[i]=(L){p[i],p[i+1]};
    for(int i=1;i<=n;i++)
        pre[i]=i-1,nxt[i]=i+1;
    pre[1]=n;nxt[n]=1;
    for(int i=1;i<=n;i++)add(i);
    for(int i=1;i<=n-2;i++)
    {
        int id=pq.top().second;
        while(del[id])
        {
            pq.pop();
            id=pq.top().second;
        }
        int L=pre[id],R=nxt[id];del[id]=1;
        ans=max(ans,pq.top().first);
        pq.pop();
        if(abs((l[L].b-l[L].a)*(l[R].b-l[R].a))<eps)break;
        P p=cp(l[L],l[R]);
        l[L].b=l[R].a=p;
        nxt[L]=R;pre[R]=L;
        add(L);add(R);
    }
    printf("%.6lf",(double)ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值