0%

动态加点的凸包

有一个二维平面上的点构成的点集。有两种操作:1)加入一个点。2)询问一个点是否在这个点集的凸包内。

方法1:分别维护上凸壳和下凸壳

对于每个凸壳,用一个$set$存下凸壳上的点,把这些点按照横坐标排好序。新加入点的时候,找出横坐标与新加入点最接近的、凸壳上的点,判断是否需要弹掉。查询的时候二分找出对应的位置。

注意一个问题:最开始的时候,我的代码是这样的:

1
2
set<Point>::iterator pre=s1.upper_bound(A),suf=s1.lower_bound(A);
// 弹掉横坐标小于等于A的不需要的点

这样可能$suf$指向的元素,在弹横坐标大于等于$A$的点的时候,已经被删掉了。

zlx告诉我,实际上不需要单独写上下凸壳的加入、查询函数,因为上凸壳按照$x$轴对称之后就是个下凸壳,可以只写维护下凸壳的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <set>
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x)
{
x=0; char c=getchar(); int f=1;
while(!isdigit(c)){if(c=='-')f=-1; c=getchar();}
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}

struct Point {
ll x,y;
Point (ll x=0,ll y=0): x(x),y(y) {}
friend Point operator +(Point A,Point B) { return Point(A.x+B.x,A.y+B.y); }
friend Point operator -(Point A,Point B) { return Point(A.x-B.x,A.y-B.y); }
friend Point operator *(Point A,ll B) { return Point(A.x*B,A.y*B); }
friend Point operator /(Point A,ll B) { return Point(A.x/B,A.y/B); }
friend bool operator < (Point A,Point B) { return A.x<B.x; }
};
ll Cross(Point A,Point B) { return A.x*B.y-A.y*B.x; }
set<Point> s1,s2;
bool be_under(Point A) {
set<Point>::iterator it1=s1.lower_bound(A),it2;
if(it1==s1.end()) return 0;
if((*it1).x==A.x) return (*it1).y>=A.y;
if(it1==s1.begin()) return 0;
it2=it1,it2--;
return Cross(A-(*it2),(*it1)-(*it2))>=0;
}
bool be_upper(Point A) {
set<Point>::iterator it1=s2.lower_bound(A),it2;
if(it1==s2.end()) return 0;
if((*it1).x==A.x) return (*it1).y<=A.y;
if(it1==s2.begin()) return 0;
it2=it1,it2--;
return Cross((*it1)-(*it2),A-(*it2))>=0;
}
void insert_down(Point A) {
if(s2.empty()) { s2.insert(A); return; }
set<Point>::iterator pre=s2.upper_bound(A),ppre;
if(pre!=s2.begin()) {
pre--;
while(pre!=s2.begin()) {
ppre=pre,--ppre;
if(Cross((*pre)-(*ppre),A-(*ppre))<=0) s2.erase(*pre),pre=ppre;
else break;
}
if(pre==s2.begin())
if((*pre).x==A.x) s2.erase(*pre);
}
set<Point>::iterator suf=s2.lower_bound(A),ssuf;
if(suf!=s2.end()) {
ssuf=suf,++ssuf;
while(ssuf!=s2.end()) {
if(Cross((*suf)-A,(*ssuf)-A)<=0) s2.erase(*suf),suf=ssuf;
else break;
++ssuf;
}
if(ssuf==s2.end())
if((*suf).x==A.x) s2.erase(*suf);
}
s2.insert(A);
}
void insert_up(Point A) {
if(s1.empty()) { s1.insert(A); return; }
set<Point>::iterator pre=s1.upper_bound(A),ppre;
if(pre!=s1.begin()) {
pre--;
while(pre!=s1.begin()) {
ppre=pre,--ppre;
if(Cross((*pre)-(*ppre),A-(*ppre))>=0) s1.erase(*pre),pre=ppre;
else break;
}
if(pre==s1.begin())
if((*pre).x==A.x) s1.erase(*pre);
}
set<Point>::iterator suf=s1.lower_bound(A),ssuf;
if(suf!=s1.end()) {
ssuf=suf,++ssuf;
while(ssuf!=s1.end()) {
if(Cross((*suf)-A,(*ssuf)-A)>=0) s1.erase(*suf);
else break;
suf=ssuf,++ssuf;
}
if(ssuf==s1.end())
if((*suf).x==A.x) s1.erase(*suf);
}
s1.insert(A);
}
void Debug() {
cout<<"upper:";
for(set<Point>::iterator it=s1.begin();it!=s1.end();++it) printf("%lld %lld ",it->x,it->y);
puts("");
cout<<"lower:";
for(set<Point>::iterator it=s2.begin();it!=s2.end();++it) printf("%lld %lld ",it->x,it->y);
puts("");
}
int main() {
int q,ty; Point p;
rd(q);
for(int i=1;i<=2;++i,q--) {
rd(ty),rd(p.x),rd(p.y);
insert_down(p),insert_up(p);
}
// Debug();
while(q--) {
rd(ty),rd(p.x),rd(p.y);
if(ty==1) {
if(!be_under(p)) insert_up(p);
if(!be_upper(p)) insert_down(p);
}
else {
if(be_under(p)&&be_upper(p)) puts("YES");
else puts("NO");
}
// Debug();
}
return 0;
}

方法2:维护极角序

首先找出头三个加入的点组成的凸包的内部的一个点,这个点在之后的任意时刻一定都在凸包内,我们就把它作为原点,按照极角序维护凸包上的点。加入一个点的时候,判断与这个点极角序相邻的点是否需要被弹掉就可以了。