W
e
l
c
o
m
e
: )

题解:CF70D Professor's task

题意

实现以下两种操作:

  1. 往点集 \(S\) 中添加一个点 \((x,y)\)

  2. 询问点 \((x,y)\) 是否在点集 \(S\) 的凸包中。

分析

动态凸包板子。

建议先完成 P2521 [HAOI2011] 防线修建

上题维护的是上半个凸包,本题维护上下两个。

将凸包中的点按 \(x\) 排序,通过 \((x,y)\) 前驱和后继连成的直线判断 \((x,y)\) 是否位于凸包内。

若在凸包外,则加入这个点,并删除当前凸包中所有不符合凸包性质的点。


考虑使用平衡树。

既然只用查前驱和后继,用 set 就能解决。

为了便于计算,定义结构体 vec

struct vec
{
    double x, y;
    vec(double X=0, double Y=0): x(X), y(Y) {}
    friend vec operator-(vec a, vec b) {return {a.x-b.x, a.y-b.y};}
    friend double cross(vec a, vec b) {return a.x*b.y-a.y*b.x;}
    bool operator<(vec b) const {return x<b.x;}
};

因为要维护上下两个凸包,再封装一个结构体存凸包。

struct convex_hull:set<vec>
{
    auto pre(iterator it) {return --it;}
    auto aft(iterator it) {return ++it;}

    bool is_up; // 记录该凸包为上凸包还是下凸包
    convex_hull(bool x): is_up(x) {}

通过前驱和后继检查该点是否在凸包内:

bool chk(vec A)
{
    auto it=lower_bound(A);
    if(it==end()) return 0;
    if(it->x==A.x) 
        return is_up?it->y>=A.y:it->y<=A.y;
    if(it==begin()) return 0;
    vec B=*it, C=*--it;
    double st=cross(B-A, B-C);
    return is_up?st<=0:st>=0;
}

从凸包中移除一个点:

bool remove(set<vec>::iterator it)
{
    if(it==begin()) return 0;
    auto itl=pre(it);
    auto itr=aft(it);
    if(itr==end()) return 0;
    vec a=*it-*itl, b=*itr-*it;
    if(is_up?cross(a, b)<0:cross(a, b)>0) return 0;
    return erase(it), 1;
}

向凸包中加入一个点:

void append(vec A)
{
    if(chk(A)) return;
    auto it=find(A);
    if(it!=end()) erase(it);
    it=insert(A).first;
    while(it!=begin()&&remove(pre(it)));
    while(aft(it)!=end()&&remove(aft(it)));
}

每个点最多进一次凸包点集,也最多出一次凸包点集,时间复杂度 \(O(n\log n)\)

AC 记录

posted @ 2024-08-25 21:07  Jimmy-LEEE  阅读(16)  评论(0)    收藏  举报