ACM幾何問題基礎知識講解(附代碼)

首先我們要理解什麼是向量,向量就是有大小和方向的量。在平面座標系中,向量用x,y表示。等於向量起點到終點的位移。以下是它們的常用定義:

 

  1. struct Point  
  2. {  
  3.     double x,y;  
  4.     Point(double x=0,double y=0):x(x),y(y){}//構造函數  
  5. };  
  6. typedef Point Vector;  
  7.   
  8. //向量+向量=向量,點+向量=點  
  9. Point operator+(Point A,Point B)  
  10. {  
  11.     return Point(A.x+B.x,A.y+B.y);  
  12. }  
  13.   
  14. //點-點=向量  
  15. Point operator-(Point A,Point B)  
  16. {  
  17.     return Point(A.x-B.x,A.y-B.y);  
  18. }  
  19.   
  20.   
  21. //向量*數=向量  
  22. Point operator*(Point A,double p)  
  23. {  
  24.     return Point(A.x*p,A.y*p);  
  25. }  
  26.   
  27.   
  28. //向量/數=向量  
  29. Point operator/(Point A,double p)  
  30. {  
  31.     return Point(A.x/p,A.y/p);  
  32. }  
  33. double eps=1e-10;  
  34.   
  35. //如果等於0,返回0,小於0返回-1,大於0,返回1  
  36. int dcmp(double x)  
  37. {  
  38.     if(fabs(x)<eps)return 0;else return x<0?-1:1;  
  39. }  
  40.   
  41. //判斷是否相等  
  42. bool operator==(const Point& a,const Point &b)  
  43. {  
  44.     return dcmp(a.x-b.x)==0&&dcmp(a.y-b.y)==0;  
  45. }  
  46.   
  47. int main()  
  48. {  
  49.     Point a,b,c;  
  50.     double p=2;  
  51.     a.x=4;  
  52.     a.y=3;  
  53.     b.x=2;  
  54.     b.y=5;  
  55.     c=a+b;//向量+向量=向量  
  56.     c=a-b;//點-點=向量  
  57.     c=a/p;//向量/數=向量  
  58.     c=a*p;//向量*數=向量  
  59.     return 0;  
  60. }  
struct Point
{
    double x,y;
    Point(double x=0,double y=0):x(x),y(y){}//構造函數
};
typedef Point Vector;

//向量+向量=向量,點+向量=點
Point operator+(Point A,Point B)
{
    return Point(A.x+B.x,A.y+B.y);
}

//點-點=向量
Point operator-(Point A,Point B)
{
    return Point(A.x-B.x,A.y-B.y);
}


//向量*數=向量
Point operator*(Point A,double p)
{
    return Point(A.x*p,A.y*p);
}


//向量/數=向量
Point operator/(Point A,double p)
{
    return Point(A.x/p,A.y/p);
}
double eps=1e-10;

//如果等於0,返回0,小於0返回-1,大於0,返回1
int dcmp(double x)
{
    if(fabs(x)<eps)return 0;else return x<0?-1:1;
}

//判斷是否相等
bool operator==(const Point& a,const Point &b)
{
    return dcmp(a.x-b.x)==0&&dcmp(a.y-b.y)==0;
}

int main()
{
    Point a,b,c;
    double p=2;
    a.x=4;
    a.y=3;
    b.x=2;
    b.y=5;
    c=a+b;//向量+向量=向量
    c=a-b;//點-點=向量
    c=a/p;//向量/數=向量
    c=a*p;//向量*數=向量
    return 0;
}


 

基本運算

點積是兩個向量v和w的點積等於二者長度的乘積在乘上它們夾角的餘弦。如果兩向量垂直,點積就等於0。兩個向量OA和OB的點積等於xa*xb+ya*yb。下面是點積計算方法,以及用點積計算向量長度和夾角的函數。

  1. //點積計算方法  
  2. double Dot(Point A,Point B)  
  3. {  
  4.     return A.x*B.x+A.y*B.y;  
  5. }  
  6. //點到原點的長度  
  7. double Lenth(Point A)  
  8. {  
  9.     return sqrt(Dot(A,A));  
  10. }  
  11. //角度計算  
  12. double Angle(Point A,Point B)  
  13. {  
  14.     return acos(Dot(A,B)/Lenth(A)/Lenth(B));  
  15. }  
//點積計算方法
double Dot(Point A,Point B)
{
    return A.x*B.x+A.y*B.y;
}
//點到原點的長度
double Lenth(Point A)
{
    return sqrt(Dot(A,A));
}
//角度計算
double Angle(Point A,Point B)
{
    return acos(Dot(A,B)/Lenth(A)/Lenth(B));
}


 

叉積就是兩個向量v和w組成的三角形的有向面積的兩倍。叉積的計算方法及三角形面積的兩倍的計算方法如下:

 

  1. //就算OA和OB的叉積  
  2. double Cross(Point A,Point B)  
  3. {  
  4.     return A.x*B.y-A.y*B.x;  
  5. }  
  6.   
  7. //計算三角形的面積的兩倍  
  8. double Area(Point A,Point B,Point C)  
  9. {  
  10.     return Cross(B-A,C-A);  
  11. }  
//就算OA和OB的叉積
double Cross(Point A,Point B)
{
    return A.x*B.y-A.y*B.x;
}

//計算三角形的面積的兩倍
double Area(Point A,Point B,Point C)
{
    return Cross(B-A,C-A);
}


 

向量的旋轉。向量可以繞起點旋轉,公式爲x'=x*cosa-y*sina,y'=x*sina+y*cosa。其中a爲逆時針旋轉的角。代碼如下:

 

  1. //向量的旋轉  
  2. Point Rotate(Point A,double rad)//rad是弧度  
  3. {  
  4.     return Point(A.x*cos(rad)-A.y*sin(rad),A.x*sin(rad)+A.y*cos(rad));  
  5. }  
//向量的旋轉
Point Rotate(Point A,double rad)//rad是弧度
{
    return Point(A.x*cos(rad)-A.y*sin(rad),A.x*sin(rad)+A.y*cos(rad));
}


 

下面的函數計算向量的單位法線,即左轉90度以後把長度歸一化。

  1. //計算向量單位法線  
  2. Point Normal(Point A)  
  3. {  
  4.     double l=Lenth(A);  
  5.     return Point(-A.y/l,A.x/l);  
  6. }  
//計算向量單位法線
Point Normal(Point A)
{
    double l=Lenth(A);
    return Point(-A.y/l,A.x/l);
}


 

點和直線 

直線上一點可以用直線上一點p0和方向向量v來表示。直線上所有點p=p0+t*v.其中t爲參數。如果已知直線上的兩個不同點A和B,則方向向量爲B-A,所以參數方程A+(B-A)*t。對於直線t沒有限制,射線t>0,線段0<t<1。

直線交點。設直線分別爲P+tv和Q+tw,設向量u=P-Q,交點在第一條直線的參數爲t1,第二條直線的參數爲t2,則x和y座標可以列出一個方程,解得:

t1=cross(w,u)/cross(v,w),t2=cross(v,u)/cross(v,w)。

代碼如下:

  1. //直線交點公式  
  2. Point  GetLineIntersection(Point P,Point V,Point Q,Point W)  
  3. {  
  4.     Point u=P-Q;  
  5.     double t=Cross(W,u)/Cross(V,W);  
  6.     return P+V*t;  
  7. }  
//直線交點公式
Point  GetLineIntersection(Point P,Point V,Point Q,Point W)
{
    Point u=P-Q;
    double t=Cross(W,u)/Cross(V,W);
    return P+V*t;
}


 

點到直線的距離。點到直線的距離是一個常用函數,可以用叉積算出,即用平行四邊形的面積除以底,代碼如下:

  1. //點到直線的距離  
  2. double DistanceToLine(Point P,Point A,Point B)  
  3. {  
  4.     Point v1=B-A,v2=P-A;  
  5.     return fabs(Cross(v1,v2))/Lenth(v1);  
  6. }  
//點到直線的距離
double DistanceToLine(Point P,Point A,Point B)
{
    Point v1=B-A,v2=P-A;
    return fabs(Cross(v1,v2))/Lenth(v1);
}


 

點到線段的距離有兩種情況,代碼如下:

  1. //點到線段的距離  
  2. double DistanceToSegment(Point P,Point A,Point B)  
  3. {  
  4.     if(A==B) return Lenth((P-A));  
  5.     Point v1=B-A,v2=P-A,v3=P-B;  
  6.     if(dcmp(Dot(v1,v2))<0)return Lenth(v2);  
  7.     else if(dcmp(Dot(v1,v3))>0)return Lenth((v3));  
  8.     else return fabs(Cross(v1,v2))/Lenth(v1);  
  9. }  
//點到線段的距離
double DistanceToSegment(Point P,Point A,Point B)
{
    if(A==B) return Lenth((P-A));
    Point v1=B-A,v2=P-A,v3=P-B;
    if(dcmp(Dot(v1,v2))<0)return Lenth(v2);
    else if(dcmp(Dot(v1,v3))>0)return Lenth((v3));
    else return fabs(Cross(v1,v2))/Lenth(v1);
}


 

 

點在直線上的投影,代碼如下:

 

  1. //點P在直線AB上的投影  
  2. Point GetLineProjecton(Point P,Point A,Point B)  
  3. {  
  4.     Point v=B-A;  
  5.     return A+v*(Dot(v,P-A)/Dot(v,v));  
  6. }  
//點P在直線AB上的投影
Point GetLineProjecton(Point P,Point A,Point B)
{
    Point v=B-A;
    return A+v*(Dot(v,P-A)/Dot(v,v));
}


 

線段相交判定。給定兩條線段,判斷是否相交。我們定義“規範相交”爲兩線段恰好有一個公共點,且不在任何一條線段的端點。線段規範相交的充要條件是:每條線段的兩個端點都在另一條線段的兩側。代碼如下:

  1. //判斷線段相交(不含端點)  
  2. bool SegmentProperIntersection(Point a1,Point a2,Point b1,Point b2)  
  3. {  
  4.     double c1=Cross(a2-a1,b1-a1),c2=Cross(a2-a1,b2-a1),  
  5.     c3=Cross(b2-b1,a1-b1),c4=Cross(b2-b1,a2-b1);  
  6.     return dcmp(c1)*dcmp(c2)<0&&dcmp(c3)*dcmp(c4)<0;  
  7. }  
//判斷線段相交(不含端點)
bool SegmentProperIntersection(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,a1-b1),c4=Cross(b2-b1,a2-b1);
    return dcmp(c1)*dcmp(c2)<0&&dcmp(c3)*dcmp(c4)<0;
}


 

如果允許在端點出相交,情況就比較複雜了,有可能共線,還有可能某個端點在另外一條線段上。爲了判斷上述情況是否發生,還需要如下一段判斷一個點是否在一條線段上(不含端點)的代碼:

  1. //判斷一個點P是否在一條線段a1a2上  
  2. bool OnSegment(Point p,Point a1,Point a2)  
  3. {  
  4.     return dcmp(Cross(a1-p,a2-p))==0&&dcmp(Dot(a1-p,a2-p));  
  5. }  
//判斷一個點P是否在一條線段a1a2上
bool OnSegment(Point p,Point a1,Point a2)
{
    return dcmp(Cross(a1-p,a2-p))==0&&dcmp(Dot(a1-p,a2-p));
}


 

多邊形

計算多邊形的面積。如果多變形是凸的,可以從第一個頂點出發把凸多邊形分成n-2個三角形,然後把面積加起來。代碼如下:

  1. //計算多邊形的面積  
  2. double ConvexPolygonArea(Point* p,int n)  
  3. {  
  4.     double area=0;  
  5.     for(int i=1;i<n-1;i++)  
  6.     area+=Cross(p[i]-p[0],p[i+1]-p[0]);  
  7.     return area/2;  
  8. }  
//計算多邊形的面積
double ConvexPolygonArea(Point* p,int n)
{
    double area=0;
    for(int i=1;i<n-1;i++)
    area+=Cross(p[i]-p[0],p[i+1]-p[0]);
    return area/2;
}


 

可以另取p[0]點爲劃分頂點,一方面可以少算兩個叉積,另一方面也減少乘法溢出的可能性,還不用特殊處理。代碼如下:

  1. double PolygonArea(Point* p,int n)//p是一個結構體數組  
  2. {  
  3.     double area=0;  
  4.     for(int i=1;i<n-1;i++)  
  5.     area+=Cross(p[i]-p[0],p[i+1]-p[0]);  
  6.     return area/2;  
  7. }  
  8. int main()  
  9. {  
  10.     Point a[4];  
  11.     a[0].x=0;  
  12.     a[0].y=0;  
  13.     a[1].x=3;  
  14.     a[1].y=0;  
  15.     a[2].x=3;  
  16.     a[2].y=3;  
  17.     a[3].x=0;  
  18.     a[3].y=3;  
  19.     double area=PolygonArea(a,4);  
  20.     printf("%lf",area);  
  21.     return 0;  
  22. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章