题意
实现以下两种操作:
往点集 \(S\) 中添加一个点 \((x,y)\)。
询问点 \((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)\)。
标签:包中,return,题解,Professor,凸包,bool,vec,auto,CF70D From: https://www.cnblogs.com/redacted-area/p/18379566