本文將同步發布於:
題目
題意概述
給你平面上的一些點和直線,有兩種操作:
- 新加入一個點 \((x,y)\);
- 給定一條直線 \(ax+by=c\),詢問是否所有點都在這條直線的同側(在直線上不合法)。
初始時有 \(n\leq 10^5\) 個點,共有 \(q\leq 10^5\) 次操作。
題解
對題意轉化
我們考慮將 所有點都在直線的同一側 這一條件進行轉化。
具體地,我們先考慮一個簡單的子問題。
簡單子問題
問題是這樣的,對於一條標准形式的直線 \(ax+by+c=0\),在它同側的點 \((x_p,y_p)\) 滿足什么性質?
我們倒推並分類討論:
- 若 \(ax_p+by_p+c=0\),顯然點 \((x_p,y_p)\) 在這條直線上;
- 若 \(ax_p+by_p+c<0\),顯然它與所有點 \(q\) 滿足 \(ax_q+by_q+c<0\) 在直線的同側;
- 若 \(ax_p+by_p+c>0\),顯然它與所有點 \(q\) 滿足 \(ax_q+by_q+c>0\) 在直線的同側;
因此我們發現,如果兩個點在一條直線的同側,則將兩點坐標代入直線方程得到的結果同號。
解決了子問題,我們對題意進行轉化,得到如下式子。
一條直線合法當且僅當
意思就是所有點對應的符號相同(暫時不考慮點在直線上)。
進一步地,一堆數同號說明它們的最大值與最小值同號。
問題轉變成為,給定一條直線 \(ax+by-c=0\),求
利用幾何性質解決問題
先來求解最大值吧。
設直線為 \(ax+by-c=\texttt{max}\)。
先將直線轉化為斜截式:
我們發現:
- 若 \(b>0\),我們只需要最大化該直線在 \(y\) 軸上的截距即可,維護一個上凸包,在上凸包上二分斜率求解即可;
- 若 \(b<0\),我們則可以通過變號等方法修改成為 \(b>0\) 的情況;
- 若 \(b=0\),我們發現此時的最大值必定在凸包的右端點取到,若用 \(\texttt{max}\) 值作為判據,則不受影響,無需考慮。
再來求解最小值,還是一樣的步驟:
設直線為 \(ax+by-c=\texttt{min}\)。
先將直線轉化為斜截式:
我們發現:
- 若 \(b>0\),我們只需要最小化該直線在 \(y\) 軸上的截距即可,維護一個下凸包,在下凸包上二分斜率求解即可;
- 若 \(b<0\),我們則可以通過變號等方法修改成為 \(b>0\) 的情況;
- 若 \(b=0\),我們發現此時的最小值必定在凸包的右端點取到,若用 \(\texttt{min}\) 值作為判據,則不受影響,無需考慮。
分治方法優化轉移
上述方法需要動態維護兩個凸包,並且凸包的橫坐標不具有單調性,這需要用 平衡樹 來維護。
考慮到本題並不強制在線,我們考慮離線下來,利用 cdq 分治來維護凸包,時間復雜度為 \(\Theta(n\log^2n)\)(視作 \(n,q\) 同階)。
具體地,我們考慮對點和直線的加入時間進行分治,假設當前要處理區間 \([l,r]\) 內的事件。我們就只要考慮 區間 \([l,\texttt{mid}]\) 內的點形成的凸包對 區間 \([\texttt{mid}+1,r]\) 內直線的貢獻即可。
維護凸包所需時間復雜度為排序的 \(\Theta(n\log_2n)\),結合主定理分析可知最終時間復雜度為 \(\Theta(n\log_2^2n)\)。
參考程序
更多細節參見參考程序。
#include<bits/stdc++.h>
using namespace std;
#define reg register
typedef long long ll;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
static char buf[1<<21],*p1=buf,*p2=buf;
#define flush() (fwrite(wbuf,1,wp1,stdout),wp1=0)
#define putchar(c) (wp1==wp2&&(flush(),0),wbuf[wp1++]=c)
static char wbuf[1<<21];int wp1;const int wp2=1<<21;
inline int read(void){
reg bool f=false;
reg char ch=getchar();
reg int res=0;
while(!isdigit(ch))f|=(ch=='-'),ch=getchar();
while(isdigit(ch))res=10*res+(ch^'0'),ch=getchar();
return f?-res:res;
}
inline ll readll(void){
reg bool f=false;
reg char ch=getchar();
reg ll res=0;
while(!isdigit(ch))f|=(ch=='-'),ch=getchar();
while(isdigit(ch))res=10*res+(ch^'0'),ch=getchar();
return f?-res:res;
}
inline void writeln(const char* s){
while(*s) putchar(*(s++));
putchar('\n');
return;
}
inline ll max(reg ll a,reg ll b){
return a>b?a:b;
}
inline ll min(reg ll a,reg ll b){
return a<b?a:b;
}
struct Vector{
int x,y;
inline Vector(reg int x=0,reg int y=0):x(x),y(y){
return;
}
inline Vector operator+(const Vector& a)const{
return Vector(x+a.x,y+a.y);
}
inline Vector operator-(const Vector& a)const{
return Vector(x-a.x,y-a.y);
}
};
inline ll dot(const Vector& a,const Vector& b){
return 1ll*a.x*b.x+1ll*a.y*b.y;
}
inline ll cross(const Vector& a,const Vector& b){
return 1ll*a.x*b.y-1ll*a.y*b.x;
}
typedef Vector Point;
inline bool operator<(const Point& a,const Point& b){
return a.x<b.x||(a.x==b.x&&a.y<b.y);
}
const int MAXN=1e5+5;
const int MAXQ=1e5+5;
const ll inf=5e18;
struct updates{
int tim;
Point p;
};
struct querys{
int tim;
int A,B;
ll C;
ll Max,Min;
};
int n,q;
int totu,totq;
updates up[MAXN+MAXQ];
querys qu[MAXQ];
inline ll getVal(const querys& q,const Point& p){
return 1ll*q.A*p.x+1ll*q.B*p.y-q.C;
}
inline void solve(reg int l,reg int r,reg int lu,reg int ru,reg int lq,reg int rq){
if(l==r)
return;
if(lu>ru||lq>rq)
return;
reg int mid=(l+r)>>1;
reg int midu,midq;
if(up[lu].tim<=mid){
reg int __l=lu,__r=ru,__mid;
while(__l<__r){
__mid=(__l+__r)>>1;
if(up[__mid+1].tim<=mid)
__l=__mid+1;
else
__r=__mid;
}
midu=__l;
}
else
midu=lu-1;
if(qu[lq].tim<=mid){
reg int __l=lq,__r=rq,__mid;
while(__l<__r){
__mid=(__l+__r)>>1;
if(qu[__mid+1].tim<=mid)
__l=__mid+1;
else
__r=__mid;
}
midq=__l;
}
else
midq=lq-1;
solve(l,mid,lu,midu,lq,midq);
if(lu<=midu&&midq+1<=rq){
reg int tot=0;
static Point tmp[MAXN+MAXQ];
for(reg int i=lu;i<=midu;++i)
tmp[++tot]=up[i].p;
sort(tmp+1,tmp+tot+1);
reg int top;
static Point S[MAXN+MAXQ];
top=0;
for(reg int i=1;i<=tot;++i){
while(top>1&&cross(S[top]-S[top-1],tmp[i]-S[top-1])>=0)
--top;
S[++top]=tmp[i];
}
for(reg int i=midq+1;i<=rq;++i){
reg int __l=1,__r=top,__mid;
while(__l<__r){
__mid=(__l+__r)>>1;
if(getVal(qu[i],S[__mid])<getVal(qu[i],S[__mid+1]))
__l=__mid+1;
else
__r=__mid;
}
qu[i].Max=max(qu[i].Max,getVal(qu[i],S[__l]));
}
top=0;
for(reg int i=1;i<=tot;++i){
while(top>1&&cross(S[top]-S[top-1],tmp[i]-S[top-1])<=0)
--top;
S[++top]=tmp[i];
}
for(reg int i=midq+1;i<=rq;++i){
reg int __l=1,__r=top,__mid;
while(__l<__r){
__mid=(__l+__r)>>1;
if(getVal(qu[i],S[__mid])>getVal(qu[i],S[__mid+1]))
__l=__mid+1;
else
__r=__mid;
}
qu[i].Min=min(qu[i].Min,getVal(qu[i],S[__l]));
}
}
solve(mid+1,r,midu+1,ru,midq+1,rq);
return;
}
int main(void){
n=read(),q=read();
for(reg int i=1;i<=n;++i){
++totu;
up[totu].tim=0,up[totu].p.x=read(),up[totu].p.y=read();
}
for(reg int i=1;i<=q;++i){
static int opt;
opt=read();
switch(opt){
case 1:{
++totu;
up[totu].tim=i,up[totu].p.x=read(),up[totu].p.y=read();
break;
}
case 2:{
++totq;
qu[totq].tim=i,qu[totq].A=read(),qu[totq].B=read(),qu[totq].C=readll(),qu[totq].Max=-inf,qu[totq].Min=inf;
if(qu[totq].B<0)
qu[totq].A=-qu[totq].A,qu[totq].B=-qu[totq].B,qu[totq].C=-qu[totq].C;
else if((!qu[totq].B)&&qu[totq].A<0)
qu[totq].A=-qu[totq].A,qu[totq].C=-qu[totq].C;
break;
}
}
}
solve(0,q,1,totu,1,totq);
for(reg int i=1;i<=totq;++i)
writeln((!qu[i].Max||!qu[i].Min||((qu[i].Max^qu[i].Min)>>63))?"NO":"YES");
flush();
return 0;
}