閔可夫斯基和:
閔可夫斯基和又稱閔可夫斯基加法,是兩個歐幾里得空間的點集的和。
點集A和點集B的閔可夫斯基和被定義為: A+B={a+b | a屬於A,屬於B}
例如,平面上有兩個三角形,其坐標分別為A={(1,0),(0,1),(0,-1)}及B = {(0, 0), (1, 1), (1, −1)},則其閔可夫斯基和為
A + B = {(1, 0), (2, 1), (2, −1), (0, 1), (1, 2), (1, 0), (0, −1), (1, 0), (1, −2)}
以上是百度百科的定義,通俗點講就是講,點集A和點集B的閔可夫斯基和就是將點集B的每個點坐標當做一個向量,然后點集A分別沿着這些向量平移,得到新的點集就是點集A與點集B的閔可夫斯基和。如下圖(畫圖太難了,隨便整了個簡單的)
設綠色部分為點集A,藍色部分為點集B,則紅色部分構成點集就是A與B的閔可夫斯基和。
例題引入:
P4557戰爭 https://www.luogu.com.cn/problem/P4557
題目描述
九條可憐是一個熱愛讀書的女孩子。
在她最近正在讀的一本小說中,描述了兩個敵對部落之間的故事。第一個部落有 n 個人,第二個部落有 m 個人,每一個人的位置可以抽象成二維平面上坐標為 (xi,yi) 的點。
在這本書中,人們有很強的領地意識,對於平面上的任何一個點,如果它被三個來自同一部落的人形成的三角形(可能退化成一條線段)包含(包括邊界),那么這一個點就屬於這一個部落的領地。如果存在一個點同時在兩個陣營的領地中,那么這兩個部落就會為了爭奪這一個點而發生戰爭。
常年的征戰讓兩個部落不堪重負,因此第二個部落的族長作出了一個英明的決定,他打算選擇一個向量 (dx,dy) ,讓所有的族人都遷徙這個向量的距離,即所有第二陣營的人的坐標都變成 (xi+dx,yi+dy) 。
現在他計划了 q 個遷徙的備選方案,他想要你來幫忙對每一個遷徙方案,計算一下在完成了遷徙之后,兩個部落之間還會不會因為爭奪領地而發生戰爭。
輸入格式
第一行輸入三個整數 n,m,q,表示兩個部落里的人數以及遷徙的備選方案數。
接下來 n 行每行兩個整數 xi,yi 表示第一個部落里的人的坐標。
接下來 m 行每行兩個整數 xi,yi 表示第二個部落里的人的坐標。
接下來 q 行每行兩個整數 dxi,dyi 表示一個遷徙方案。
輸入數據保證所有人的坐標兩兩不同。
輸出格式
對於每個遷徙方案,輸出一行一個整數,0 表示不會發生沖突,1 表示會發生沖突。
巴拉巴拉一大堆,題意其實就是給出兩個點集A,B,以及q次詢問。對於每次詢問,給出一個向量,然后點集B向向量平移,若平移后的點集A與點集B構成的兩個多邊形存在交點則輸出1,否則輸出0。
解題思路:
對於向量 p,若點集B構成的多邊形向向量 p 平移后與點集A構成的多邊形存在交點,那么就可以得出: 存在 b+p=a (b屬於B, a屬於A)
轉化一下可以得到當A、B存在交點時,向量 p 屬於{ a-b | a屬於A, b屬於B}
因此我們可以來構造閔可夫斯基和:C={ a + (-b) } (在輸入B的坐標時全部取反即可)
構造完后,問題就變成了給出q次詢問,若一個給出向量在C的內部則說明存在交點,若不在則說明不存在交點。
具體步驟:
1、構造 A,B 的凸包
2、求解A和-B的閔可夫斯基和C
3、對C再求一次凸包
4、每次詢問二分判斷向量是否在 C 的凸包內 (時間復雜度qlogn級別)
AC代碼:
(傻逼scanf時少了個%,debug了兩個多小時)
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<vector> #include<queue> #include<stack> #include<map> #define INF 0x3f3f3f3f using namespace std; typedef long long LL; typedef unsigned long long ULL; const int maxn=1e5+6; const int mod=1e9+7; const double pi=acos(-1.0); const double eps=1e-8; struct Point{ double x,y; Point(double x=0, double y=0):x(x),y(y) {}; }; typedef Point Vector; Point A[maxn],B[maxn],C[maxn],v1[maxn],v2[maxn]; int stk[maxn]; int top=0,cnt=0; int sgn(double x){ if(fabs(x)<eps) return 0; if(x<0) return -1; return 1; } bool operator == (const Point &a, const Point &b){ if(sgn(a.x-b.x)==0&&sgn(a.y-b.y)==0) return true; else return false; } Vector operator - (Point A, Point B){ return Vector(A.x-B.x, A.y-B.y); } Vector operator + (Vector A, Vector B){ return Vector(A.x+B.x, A.y+B.y); } double Dot(Vector A,Vector B){ //銳角為正,鈍角為負,直角為0 return A.x*B.x + A.y*B.y; } double Cross(Vector v0, Vector v1){ return v0.x*v1.y - v0.y*v1.x; } double Dis(Point p1, Point p2){ return sqrt((p2.x-p1.x)*(p2.x-p1.x) + (p2.y-p1.y)*(p2.y-p1.y)); } double Length(Vector t){ return t.x*t.x+t.y*t.y; } bool cmp1(const Point &A,const Point &B) { return sgn(A.y-B.y)<0||((sgn(A.y-B.y)==0&&sgn(A.x-B.x)<0)); } //極角排序,角度相同的則距離小的在前面 bool cmp2(const Point &A, const Point &B){ return sgn(Cross(A,B))>0||(sgn(Cross(A,B))==0&&sgn(Length(A)-Length(B))<0); } //GrahamScan算法求凸包 void Graham(Point *lst,int &n){ sort(lst+1,lst+n+1,cmp1); Point bs=lst[1]; top=1; stk[top]=1; for(int i=1;i<=n;i++) lst[i]=lst[i]-bs; sort(lst+2, lst+n+1, cmp2);//極角排序 for(int i=2;i<=n;i++){ while(top>1&&sgn(Cross(lst[i]-lst[stk[top-1]],lst[stk[top]]-lst[stk[top-1]]))>=0) top--; stk[++top]=i; } for(int i=1;i<=top;i++) lst[i]=lst[stk[i]]+bs; n=top; lst[top+1]=lst[1]; return ; } void Minkowski(int n,int m){ //求閔可夫斯基和 for(int i=1;i<=n;i++) v1[i]=A[i+1]-A[i]; for(int i=1;i<=m;i++) v2[i]=B[i+1]-B[i]; C[cnt=1]=A[1]+B[1]; int p1=1,p2=1; while(p1<=n&&p2<=m){ ++cnt; if(sgn(Cross(v1[p1],v2[p2]))>=0) C[cnt]=C[cnt-1]+v1[p1++]; else C[cnt]=C[cnt-1]+v2[p2++]; } while(p1<=n){ ++cnt; C[cnt]=C[cnt-1]+v1[p1++]; } while(p2<=m){ ++cnt; C[cnt]=C[cnt-1]+v2[p2++]; } } int pan_PL(Point p,Point a,Point b){ //判斷點是否在直線上 return !sgn(Cross(p-a,p-b))&&sgn(Dot(p-a,p-b))<0; } int in(Point *P,int n,Point a){ if(sgn(Cross(a-P[1],P[2]-P[1]))>0||sgn(Cross(P[n]-P[1],a-P[1]))>0) return 0; if(pan_PL(a,P[1],P[2])||pan_PL(a,P[1],P[n])) return 1; int l=2,r=n-1; while(l<r){//二分找到一個位置pos使得P[1]_A在P[1_pos],P[1_(pos+1)]之間 int mid=l+r+1>>1; if(sgn(Cross(a-P[1],P[mid]-P[1]))>0) r=mid-1; else l=mid; } return sgn(Cross(a-P[r],P[r+1]-P[r]))<=0; } int main(){ int n,m,q; scanf("%d%d%d",&n,&m,&q); for(int i=1;i<=n;i++) scanf("%lf%lf",&A[i].x,&A[i].y); Graham(A,n); for(int i=1;i<=m;i++){ scanf("%lf%lf",&B[i].x,&B[i].y); B[i].x=-B[i].x; B[i].y=-B[i].y; } Graham(B,m); Minkowski(n,m); Graham(C,cnt); while(q--){ Point t; scanf("%lf%lf",&t.x,&t.y); printf("%d\n",in(C,cnt,t)); } return 0; }
CF87E Mogohu-Rea Idol:https://www.luogu.com.cn/problem/CF87E
題目描述
A long time ago somewhere in the depths of America existed a powerful tribe governed by the great leader Pinnie-the-Wooh. Once the tribe conquered three Maya cities. Pinnie-the-Wooh grew concerned: there had to be some control over the conquered territories. That's why he appealed to the priests of the supreme god Mogohu-Rea for help.
The priests conveyed the god's will to him: to control these three cities he should put an idol to Mogohu-Rea — that will create a religious field over the cities. However, the idol is so powerful that it can easily drive the people around it mad unless it is balanced by exactly three sacrifice altars, placed one in each city. To balance the idol the altars should be placed so that the center of mass of the system of these three points coincided with the idol. When counting the center of mass consider that all the altars have the same mass.
Now Pinnie-the-Wooh is thinking where to put the idol. He has a list of hills, that are suitable to put an idol there. Help him to identify on which of them you can put an idol without risking to fry off the brains of the cities' population with the religious field.
Each city has a shape of a convex polygon such that no three vertexes lie on a straight line. The cities can intersect. Each altar should be attached to the city through a special ceremony, besides, it must be situated on the city's territory (possibly at the border). Thus, there may be several altars on a city's territory, but exactly one of them will be attached to the city. The altars, the idol and the hills are points on the plane, some of them may coincide.
The hills are taken into consideration independently from each other, the altars' location for different hills may also be different.
輸入格式
First follow descriptions of the three cities, divided by empty lines. The descriptions are in the following format:
The first line contains an integer nn , which represent the number of the polygon's vertexes ( 3<=n<=5·10^{4}3<=n<=5⋅104 ). Next nn lines contain two integers x_{i}xi , y_{i}yi each, they are the coordinates of the polygon's ii -th vertex in the counterclockwise order.
After the cities' description follows the integer mm ( 1<=m<=10^{5}1<=m<=105 ), which represents the number of hills. Next mm lines each contain two integers x_{j}xj , y_{j}yj , they are the coordinates of the jj -th hill.
All the coordinates in the input data do not exceed 5·10^{8}5⋅108 in the absolute value.
輸出格式
For each hill print on a single line "YES" (without the quotes) or "NO" (without the quotes), depending on whether the three sacrifice altars can be put to balance the idol or not.
題意翻譯
按逆時針順序給出三個凸包點集 A,B,C,每次查詢給出點 q,問是否存在點 a∈A,b∈B,c∈C 滿足 q 為 Δabc 的重心。
解題思路:
若p為Δabc的重心,則由三角形重心的定義可知,p=(a+b+c)/3,因此只需要求出點集A、B、C的閔可夫斯基和D,然后判斷3*p是否在D的內部即可。
AC代碼:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<vector> #include<queue> #include<stack> #include<map> #define INF 0x3f3f3f3f using namespace std; typedef long long LL; typedef unsigned long long ULL; const int maxn=5e5+6; const int mod=1e9+7; const double pi=acos(-1.0); const double eps=1e-9; struct Point{ double x,y; Point(double x=0, double y=0):x(x),y(y) {}; }; typedef Point Vector; Point F[maxn],p[maxn],p1[maxn],p2[maxn],p3[maxn],v1[maxn],v2[maxn]; int stk[maxn]; int top=0,cnt; int sgn(double x){ if(fabs(x)<eps) return 0; if(x<0) return -1; return 1; } bool operator == (const Point &a, const Point &b){ if(sgn(a.x-b.x)==0&&sgn(a.y-b.y)==0) return true; else return false; } Vector operator - (Point A, Point B){ return Vector(A.x-B.x, A.y-B.y); } Vector operator + (Vector A, Vector B){ return Vector(A.x+B.x, A.y+B.y); } //向量與常數的除法 Vector operator / (Vector A, double p){ return Vector(A.x/p, A.y/p); } Vector operator * (Vector A, double p){ return Vector(A.x*p, A.y*p); } double Dot(Vector A,Vector B){ //銳角為正,鈍角為負,直角為0 return A.x*B.x + A.y*B.y; } double Cross(Vector v0, Vector v1){ return v0.x*v1.y - v0.y*v1.x; } double Dis(Point p1, Point p2){ return sqrt((p2.x-p1.x)*(p2.x-p1.x) + (p2.y-p1.y)*(p2.y-p1.y)); } double Length(Vector t){ return t.x*t.x+t.y*t.y; } bool cmp1(const Point &A,const Point &B) { return sgn(A.y-B.y)<0||((sgn(A.y-B.y)==0&&sgn(A.x-B.x)<0)); } //極角排序,角度相同的則距離小的在前面 bool cmp2(const Point &A, const Point &B){ return sgn(Cross(A,B))>0||(sgn(Cross(A,B))==0&&sgn(Length(A)-Length(B))<0); } //GrahamScan算法求凸包 void Graham(Point *lst,int &n){ sort(lst+1,lst+n+1,cmp1); Point bs=lst[1]; top=1; stk[top]=1; for(int i=1;i<=n;i++) lst[i]=lst[i]-bs; sort(lst+2, lst+n+1, cmp2);//極角排序 for(int i=2;i<=n;i++){ while(top>1&&sgn(Cross(lst[i]-lst[stk[top-1]],lst[stk[top]]-lst[stk[top-1]]))>=0) top--; stk[++top]=i; } for(int i=1;i<=top;i++) lst[i]=lst[stk[i]]+bs; n=top; lst[top+1]=lst[1]; return ; } void Minkowski(Point *A,int n,Point *B,int m,Point *C){ //求閔可夫斯基和 for(int i=1;i<=n;i++) v1[i]=A[i+1]-A[i]; for(int i=1;i<=m;i++) v2[i]=B[i+1]-B[i]; cnt=1; C[cnt]=A[1]+B[1]; int p1=1,p2=1; while(p1<=n&&p2<=m){ ++cnt; if(sgn(Cross(v1[p1],v2[p2]))>=0) C[cnt]=C[cnt-1]+v1[p1++]; else C[cnt]=C[cnt-1]+v2[p2++]; } while(p1<=n){ ++cnt; C[cnt]=C[cnt-1]+v1[p1++]; } while(p2<=m){ ++cnt; C[cnt]=C[cnt-1]+v2[p2++]; } } int pan_PL(Point p,Point a,Point b){ return !sgn(Cross(p-a,p-b))&&sgn(Dot(p-a,p-b))<0; } int in(Point *P,int n,Point a){ if(sgn(Cross(a-P[1],P[2]-P[1]))>0||sgn(Cross(P[n]-P[1],a-P[1]))>0) return 0; if(pan_PL(a,P[1],P[2])||pan_PL(a,P[1],P[n])) return 1; int l=2,r=n-1; while(l<r){//二分找到一個位置pos使得P[1]_A在P[1_pos],P[1_(pos+1)]之間 int mid=l+r+1>>1; if(sgn(Cross(a-P[1],P[mid]-P[1]))>0) r=mid-1; else l=mid; } return sgn(Cross(a-P[r],P[r+1]-P[r]))<=0; } int main(){ int n1,n2,n3; scanf("%d",&n1); for(int i=1;i<=n1;i++) scanf("%lf%lf",&p1[i].x,&p1[i].y); scanf("%d",&n2); for(int i=1;i<=n2;i++) scanf("%lf%lf",&p2[i].x,&p2[i].y); scanf("%d",&n3); for(int i=1;i<=n3;i++) scanf("%lf%lf",&p3[i].x,&p3[i].y); Graham(p1,n1); Graham(p2,n2); Graham(p3,n3); Minkowski(p1,n1,p2,n2,p); int tot=cnt; Graham(p,tot); Minkowski(p,tot,p3,n3,F); Graham(F,cnt); int T; scanf("%d",&T); while(T--){ Point t; scanf("%lf%lf",&t.x,&t.y); if(in(F,cnt,t*3.0)) printf("YES\n"); else printf("NO\n"); } return 0; }