1.基礎知識
1.1關於浮點誤差
一般的話要設一個
//精度控制
bool Dcmp(double x)
{
if(fabs(x)<Eps) return 0;
else return 1;
}
1.2向量基礎運算
1.2.1點積
double Dot(Vector A,Vector B) { return A.x*B.x+A.y*B.y; }
點積可以用來判斷線段垂直什麼的.
1.2.2叉積
double Cross(Vector A,Vector B){ return A.x*B.y-A.y*B.x; }
叉積表示兩個向量組成的平行四邊形的有向面積.
設兩個向量
當 在 順時針方向時,叉積大於0.
當 在 逆時針方向時,叉積小於0.
當 共線時,叉積等於0.
1.2.3叉積點積方向.
2.進階知識
2.1點直線線段關係
2.1.1點到直線的距離
一般的話要三個點,點一個點,直線上兩個點。
直接用叉積求出面積來除以底邊的長度就可以
//點到直線距離
double PointDisToLine1(Point P,Point A,Point B)
{
Vector v1=B-A,v2=P-A;
return fabs(Cross(v1,v2))/Length(v1);
}
2.1.2點到線段的距離
點到線段的距離分爲三種情況:
紅色爲點到線段距離
1.當點位於線段上方時.
2.當點位於線段左邊時.
3.當點位於線段右邊時.
//點到線段距離
double PointDisToLine2(Point P,Point A,Point B)
{
if(A==B)
return Length(P-A);
Vector v1=B-A,v2=P-A.v3=P-B;
if(Dcmp(v1,v2)<0) return Length(v2);
else if(Dcmp(v1,v3)<0) return Length(v3);
else return fabs(Cross(v1,v2))/Length(v1);
}
2.1.3線段相交
我們判斷線段相交直接判斷一個線段兩個端點是否位於另一個線段的兩側就可以.
假如線段相交的話,那麼 叉乘 和 叉乘 的符號一定不同,
同理另一符號也一定不同,所以我們可以通過叉積的符號來判斷線段相交.
//線段相交判定
bool LineIntersection(Point a1,Point a2,Point b1,Point b2)
{
double c1=Cross(a2-a1,b1-a1),c2=Cross(a2-a1,b2-a1),
c3=Cross(b2-b1,a2-b1),c4=Cross(b2-b1,a2-b1);
return Dcmp(c1)*Dcmp(c2)<0 && Dcmp(c3)*Dcmp(c4)<0;
}
2.1.4直線交點的計算
對於直線我們設直線的方程 ,其中 , 表示方向向量
我們設兩個直線分別是 和 ,設
則
帶入求解即可.
//直線交點的計算
Point GetLineIntersection(Point P,Vector v,Point Q,Vector w)
{
Vector u=P-Q;
double t=Cross(w,u)/Cross(v,w);
return P+v*t;
}
2.2多邊形
2.2.1多邊形的面積
怎樣計算多邊形的面積呢?
我們可以把多邊形來劃分成數個三角形,然後求叉積就可以了.
對於上圖,我們要求 的話,我們只需要求出 即可
這個可以通過 來實現,實現起來比較簡單.
上面的例子是凸多邊形,假如我們要求的不是凸多邊形呢?
如下圖:
我們設
我們發現由於我們計算的時候是按照頂點的順序來計算的,所以我們計算叉積的時候會存在正負的關係,所以對於不是凸多邊形的情況,我們依然可以按照上面的方法計算比如說上圖 ,我們計算 的時候恰好叉積爲負。
最後不要忘記除2哦,因爲計算的是平行四邊形的有向面積
//多邊形面積
double S_ploy(Point p[],int n)
{
double S=0;
for(int i=3;i<=n;i++)
S+=Cross(p[i]-p[1],p[i-1]-p[1]);
return S/2;
}
2.2.2關於點在多邊形內的判斷.
一般的話採用射線法,我們只需要判斷穿過多邊形的數目就可以
//判斷點是否在多邊形內
int Point_In_Poly(Point P,Point poly[],int num)
{
int ans=0,k,d1,d2;
for(int i=1;i<=num;i++)
{
if(!Dcmp(Point_Dis_To_1(P,poly[i],poly[i%num+1]))) return -1;
k=Dcmp(Cross(ploy[i%num+1]-poly[i],p-poly[i]));
d1=Dcmp(ploy[i].y-p.y);
d2=Dcmp(poly[i%num+1].y-p.y);
if(k>0 && d1<=0 && d2>0 ) ++ans;
if(k<0 && d1>=0 && d2<0) --ans;
}
if(ans)
return 1;
return 0;
}
3.高級知識
3.1凸包
3.1.1定義
凸包是啥呢?
給定二維平面上的點集,凸包就是將最外層的點連接起來構成的凸多邊形,它能包含點集中所有的點.
上面紅色的就是一個凸包。
3.1.2Andrew算法
是 算法的變種,但是 更快.
算法流程:
1.就是先對所有點進行排序,以 爲第一關鍵字,以 爲第二關鍵字
2.按照順序依次遍歷所有點,看是否符合要求:
怎麼看是否符合要求呢,我們假設 已經是凸包中的點了,現在我們將要插入 ,這是我們要判斷凸包中倒數第一個點與倒數第二個點組成的向量,與即將插入的點與倒數第二個點組成的向量的叉積的正負,若爲負,也就是上圖中的情況 ,我們就退出凸包中的最後一個點,因爲這個點已經不可能在凸包內了,然後我們不停的判斷直達叉積大於0,就可以把當前要插入的點插入了。
搞完這一個點之後就是這個樣子了:
3.這樣按照排序遍歷一遍之後我們只是求得了下凸包,也就是凸包的一半,所以我們還要再倒序枚舉一遍,求出上凸包來
4.凸包就求完了。
//凸包
int ConvexHull(Point P[],int n)
{
sort(p,p+n);
int m=0;
for(int i=;i<n;i++)
{
while(m>1 && Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])<=0) m--;
ch[m++]=p[i];
}
int k=m;
for(int i=n-2;i>=0;i--)
{
while(m>k) && Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2]<=0) m--;
ch[m++]=p[i];
}
if(n>1) m--;
return m;
}
3.2旋轉卡殼
不要問我怎麼讀,我也不會讀..
3.2.1旋轉卡殼
旋轉卡殼是用來求平面內,最遠點的距離的(就是距離最遠的兩個點的距離)。
有一個性質非常顯然:最遠點對一定在凸包上,這種需要我們先求出凸包來.
考慮暴力枚舉:
然後我們來看旋轉卡殼:
//旋轉卡殼
double RotatingCaliper(Point ch[],int n)
{
int q=1; double ans=0;
ch[n]=ch[0];
for(int p=0;p<n;p++)
{
while(fabs(Cross(ch[q+1]-ch[p+1],ch[p]-ch[p+1])) > fabs(Cross(ch[q]-ch[p+1],ch[p]-ch[p+1]))) q=(q+1)%n;
ans=max(ans,max(Length2(ch[p]-ch[q]),Length2(ch[p+1]-ch[q+1])));
ans=max(ans,max(Length2(ch[p]-ch[q+1]),Length2(ch[p+1]-ch[q])));
}
return ans;
}
3.3半平面交
半平面交
極角:從x正半軸旋轉到當前直線的角度 函數cmath atan2()
極角排序:
用雙端隊列維護
然後每一個考慮加入一條直線,
如果新加入的直線的與倒數第二條直線的交點在倒數第一條直線與倒數第二條直線的交點在新加入直線的右邊那麼把倒數第一條直線退隊.還要和隊首判斷一下,假如隊首第一條直線的交點和隊首第二條直線的交點在新加入直線的右邊退隊..
然後拿隊首第一條直線和倒數第二條直線判斷一下就可以.
//半平面交
struct Line()
{
Point P;
Vector v;
double ang;
Line() {}
Line(Point P,Vector v) : P(P),v(v){ ang=atan2(v.y,v.x); }
bool operator < (const Line &L) const
{
return ang<L.ang;
}
}
bool OnLeft(Line L,Point p)
{
return Cross(L.v,p-L.P)>0;
}
int HalfPlane(Line L[],int n,Point poly[])
{
sort(L,L+n);
int first,last;
Point p[MAXN];
Line q[MAXN];
q[first=last=0]=L[0];
for(int i=1;i<n;i++)
{
while(first<last && !OnLeft(L[i],p[last-1]))) last--;
while(first<last && !OnLeft(L[i],p[first])) first++;
q[++last]=L[i];
if(!Dcmp(Cross(q[last].v,q[last-1].v)))
{
last--;
if(OnLeft(q[last],L[i].P))
q[last]=L[i];
}
if(first<last) p[last-1]=GetLineIntersection(q[last-1],q[last]);
}
if(last-first<=1) return 0;
p[last]=GetLineIntersection(q[last],q[first]);
int m=0;
for(int i=first;i<=last;i++) poly[m++]=p[i];
return m;
···;
}
3.4最小圓覆蓋
3.5最近點對
就是求平面中最近的兩個點,怎麼求呢可以 暴力,還可以用分治的方法來求,怎麼分治,每一次分成左邊和右邊,最近點對只有三種情況一種是在左邊,一種是在右邊,一種是在中間,所以我們先求出左邊和右邊的最近點對,跨過中間的最近點對只能在左右的距離,肯定小於左右最近點對距離然後把在範圍的點按y排序,求出距離來就可以了.
double Closest_Pair(int left,int right)
{
double dis=INF;
if(left==right)
return dis;
if(left+1==right)
return dist(p[left],p[right]);
int mid=(left+right)>>1;
double dis1=Closest_Pair(left,mid);
double dis2=Closest_Pair(mid,right);
dis=min(dis1,dis2);
int num=0;
for(int i=left;i<=right;i++)
{
if(fabs(p[mid].x-p[i].x<=dis))
now[++num]=p[i];
}
sort(now+1,now+num+1,Y);
for(int i=1;i<=num;i++)
{
for(int j=i+1;j<=num && now[j].y-now[i].y<dis;j++)
{
double dis3=Dis(now[i],now[j]);
dis=min(dis,dis3);
}
}
return dis;
}