一道融合了好多計算幾何技巧的題目ide
> Linked 洛谷 P4557ui
給定兩組散點 \(A,B\),給定 \(Q\) 組詢問,每次詢問給出向量 \(v=(dx,dy)\):
求將 \(B\) 的散點所有移動 \(v\),獲得新的散點集 \(B'\),是否存在 \(A\) 中的三個點構成的三角形與 \(B\) 中的三點構成的三角形有公共點。spa
\(|A|,|B|\le 10^5\),\(Q\le10^5\)。code
「存在 \(A\) 中三點和 \(B\) 中三點,使得構成的三角形有公共點」,等價於 \(A\) 構成的凸包與 \(B\) 構成的凸包有公共點。由於凸包包含散點集中的任意一個點,也就包含散點集中任意三個點構成的三角形。blog
因而第一步很是天然的,分別求出 \(A,B\) 的凸包,Andrew或者Graham隨便,可是用Andrew比較好的是能夠刪掉凸包邊上的點。排序
Hint.rem
那麼在下面的解析中,默認 「$A,B$」 指的是散點集求得的凸包。get
怎麼判斷凸包有沒有交?若是時間複雜度比較寬鬆,就能夠檢測 \(A\) 的每一個頂點,看是否在 \(B\) 裏面,再檢測 \(B\) 的頂點是否在 \(A\) 裏面,這樣最優複雜度也只能是 \(O(n\log n)\) 一次。顯然不可過。input
因而就要用到一個黑科技,叫作閩可夫斯基和(Minkowski);兩個凸包 \(A,B\) 的閩可夫斯基和定義以下:string
能夠感覺到它的幾何意義就是「把凸包 \(B\) 沿着凸包 \(A\) 的邊緣平移一圈獲得的封閉圖形」,那麼顯然這個封閉圖形仍然是個凸包。舉個簡單的例子,將下圖的黑色凸包和綠色凸包作閔可夫斯基和就能夠獲得橙色凸包:
這個有什麼用?這是將兩個凸包「相加」,能不能兩個凸包「相減」?
咱們把 \(B\) 以原點爲對稱中心對稱獲得 \(B^-\),那麼咱們對 \(A\) 和 \(B^-\) 作閔可夫斯基和就是:
由於 \(A,B^-\) 都是凸包,因此 \(C\) 也是凸包。而 \(C\) 就很是有用了,若是原點包含在 \(\mathbf C\) 中,那麼 \(A,B\) 就有交點。
因而第二步就是要對 \(A,B^-\) 求閔可夫斯基和獲得 \(C\)。
怎麼求閔可夫斯基和?咱們只須要找到 \(C\) 的邊界,具體步驟以下:
這樣 \(p,q\) 一直移動就會將 \(A,B\) 的每一個點都遍歷到,而後每次獲得的 \(w\) 都是 \(C\) 邊界上的點,特殊處理一下能夠獲得 \(C\) 的頂點,具體能夠看代碼。
Hint.
下面簡記「凸包 $A$ 的每一個點移動向量 $v$」獲得的圖形爲 $A+v$。
以及對於兩點 $p,q$,記 $p\pm q=(x_p\pm x_q,y_p\pm y_q)$
再看一下題目的要求,即 \(A\) 和 \(B+v\) 沒有交;根據閩可夫斯基和就是判斷
也就是判斷 \((O+v)\) 這個點在不在凸包 \(C\) 內了。
最後一步,對 \(C\) 進行極角排序,以下圖:
仍然是找到 \(C\) 的左下角的點 \(O\),而後向凸包的其餘點引出射線。二分找到 \((O+v)\) 所在的位置(位於哪兩條射線之間),而後用叉積判斷一下點在線段的哪一側便可。
/*Lucky_Glass*/ #include<cmath> #include<cstdio> #include<cstring> #include<cassert> #include<algorithm> using namespace std; namespace GEO{ typedef long long llong; const double EPS=1e-15; inline llong square(const int &key){return 1ll*key*key;} inline int sgn(const llong &key){ if(!key) return 0; return key<0? -1:1; } inline int sgn(const int &key){ if(!key) return 0; return key<0? -1:1; } inline int sgn(const double &key){ if(fabs(key)<EPS) return 0; return key<0? -1:1; } struct Vector{ int x,y; Vector(int _x=0,int _y=0):x(_x),y(_y){} friend double angle(const Vector &u,const Vector &v){ return acos(double((long double)dot(u,v)/(long double)u.len()/(long double)v.len())); } friend llong dot(const Vector &u,const Vector &v){return 1ll*u.x*v.x+1ll*u.y*v.y;} friend llong cross(const Vector &u,const Vector &v){return 1ll*u.x*v.y-1ll*u.y*v.x;} double len()const{return sqrt(square(x)+square(y));} }; struct Point{ int x,y; Point(int _x=0,int _y=0):x(_x),y(_y){} Vector operator -(const Point &v)const{return Vector(x-v.x,y-v.y);} Point operator +(const Vector &v)const{return Point(x+v.x,y+v.y);} friend llong distPoint2(const Point &u,const Point &v){return square(u.x-v.x)+square(u.y-v.y);} }; struct Line{ Point p;Vector d; Line(){} Line(Point _p,Vector _d):p(_p),d(_d){} Line(Point s,Point t):p(s),d(t-s){} }; //1=left / 0=on / -1=right int fixSide(const Point &s,const Point &t,const Point now){ return sgn(cross(t-s,now-s)); } bool cmpPointToX(const Point &u,const Point &v){ return sgn(u.x-v.x)? sgn(u.x-v.x)<0:sgn(u.y-v.y)<0; } bool cmpPointToY(const Point &u,const Point &v){ return sgn(u.y-v.y)? sgn(u.y-v.y)<0:sgn(u.x-v.x)<0; } void buildConvex(Point *org,int n,Point *res,int &nres){ nres=0; sort(org,org+n,cmpPointToX); for(int i=0;i<n;i++){ while(nres>1 && fixSide(res[nres-2],res[nres-1],org[i])<=0) nres--; res[nres++]=org[i]; } int tmp=nres; for(int i=n-2;~i;i--){ while(nres>tmp && fixSide(res[nres-2],res[nres-1],org[i])<=0) nres--; res[nres++]=org[i]; } nres--; } void modelizeConvex(Point *org,int n){ int it=0; for(int i=1;i<n;i++) if(cmpPointToY(org[i],org[it])) it=i; rotate(org,org+it,org+n); } void Minkowski(Point *pa,int na,Point *pb,int nb,Point *res,int &nres){ //把凸包的左下角固定爲凸包的第一個元素 modelizeConvex(pa,na),modelizeConvex(pb,nb); pa[na]=pa[0],pb[nb]=pb[0]; int ma=0,mb=0;nres=0; Point now=Point(pa[0].x+pb[0].x,pa[0].y+pb[0].y); res[nres++]=now; while(ma<na && mb<nb){ int re=sgn(cross(pa[ma+1]-pa[ma],pb[mb+1]-pb[mb])); //若是兩條邊極角相同,則一塊兒平移 //這樣可使獲得的點都是凸包的頂點 if(!re) now=now+(pa[ma+1]-pa[ma])+(pb[mb+1]-pb[mb]),ma++,mb++; else if(re>0) now=now+(pa[ma+1]-pa[ma]),ma++; else now=now+(pb[mb+1]-pb[mb]),mb++; res[nres++]=now; } while(ma<na){ now=now+(pa[ma+1]-pa[ma]),ma++; res[nres++]=now; } while(mb<nb){ now=now+(pb[mb+1]-pb[mb]),mb++; res[nres++]=now; } nres--; } } using namespace GEO; const int N=1e5+10; int nA,nB,nply,cas,mA,mB; Point ply[N<<1],covA[N<<1],covB[N]; bool ifFarthar(const Point &u,const Point &v){ return distPoint2(ply[0],u)<distPoint2(ply[0],v); } bool ifOutside(const Point &it){ //有可能點不位於射線之間,先特判掉 if(fixSide(ply[0],ply[1],it)<0 || fixSide(ply[0],ply[nply-1],it)>0) return true; if(fixSide(ply[0],ply[1],it)==0) return ifFarthar(ply[1],it); if(fixSide(ply[0],ply[nply-1],it)==0) return ifFarthar(ply[nply-1],it); Vector vit=it-ply[0]; int lef=1,rig=nply-1; while(lef+1<rig){ //用叉積判斷點在射線哪邊 int mid=(lef+rig)>>1,re=sgn(cross(ply[mid]-ply[0],vit)); //點在射線上 if(!re) return ifFarthar(ply[mid],it); if(re<0) rig=mid; else lef=mid; } //判斷點在線段哪一側 int re=sgn(cross(ply[lef]-it,ply[rig]-it)); return re<0; } int main(){ // freopen("input.in","r",stdin); scanf("%d%d%d",&nA,&nB,&cas); for(int i=0;i<nA;i++) scanf("%d%d",&ply[i].x,&ply[i].y); //先對 A,B 求凸包 buildConvex(ply,nA,covA,mA); for(int i=0;i<nB;i++){ scanf("%d%d",&ply[i].x,&ply[i].y); ply[i].x*=-1,ply[i].y*=-1; } buildConvex(ply,nB,covB,mB); //求出閔可夫斯基和 Minkowski(covA,mA,covB,mB,ply,nply); for(int t=1;t<=cas;t++){ Point mov;scanf("%d%d",&mov.x,&mov.y); //判斷點是否在凸包內 printf("%d\n",!ifOutside(mov)); } return 0; }
> Linked 餘命3日少女-網易雲