計算幾何 學習筆記
部分參考oi-wiki
以下知識需要高中必修4作鋪墊
判定一個整數是正數還是負數:
由於精度問題,設eps。eps是一個較小的數。
點的表示:使用一個結構體\((x,y)\)表示\(x,y\)座標
struct pt{
int x,y;
};
向量的表示:使用一個結構體\((x,y)\)表示向量的座標式\(\overrightarrow{A}=(x,y)\)
程式碼實現可以這樣:
typedef vc pt;
運演算法則:
點+向量=點
點-向量=點
向量+向量=向量
向量-向量=向量
向量*數量=向量(點乘)
pt operator +(pt x,vc y){ return (pt){x.x+y.x,x.y+y.y}; } pt operator -(pt x,vc y){ return (pt){x.x-y.x,x.y-y.y}; } vc operator +(vc x,vc y){ return (vc){x.x+y.x,x.y+y.y}; } vc operator -(vc x,vc y){ return (vc){x.x-y.x,x.y-y.y}; } vc operator *(vc x,double y){ return (vc){x.x*y,x.y*y}; } vc operator /(vc x,double y){ return (vc){x.x/y,x.y/y}; }
向量的模:
double di(pt x){
return sqrt(x.x*x.x+x.y*x.y);
}
點積式
向量\((x,y)\cdot(a,b)=x*a+y*b\)
如果\((x,y)=\overrightarrow{A},(z,a)=\overrightarrow{B}\)
則\(\overrightarrow{A}\cdot\overrightarrow{B}=|A|\cdot|B|\cdot\cos<A,B>\)
叉積式:
向量\((x,y)\cdot(a,b)=x*b-y*a\)
如果\((x,y)=\overrightarrow{A},(z,a)=\overrightarrow{B}\)
則\(\overrightarrow{A}\times\overrightarrow{B}=|A|\cdot|B|\cdot\sin<A,B>\)
int dt(vc x,vc y){
return x.x*y.x+x.y*y.y;
}
int cr(vc x,vc y){
return x.x*y.y-x.y*y.x;
}
點積可以用於判定垂直,也可用於求兩個向量的夾角。
\(\cfrac{\overrightarrow{A}\cdot\overrightarrow{B}}{|A|\cdot|B|}=\cos<A,B>\)
當\(A\perp B,\cos<A,B>=0\)
\(\overrightarrow{A}\cdot\overrightarrow{B}=|A|\cdot|B|\cdot\cos<A,B>=0\)
叉積可以用於判定平行,還可以用於求出三角形的有向面積。
三角形面積公式\(ab\sin(\angle{ab})\),所以叉積可以用於求有向面積。
\(\cfrac{\overrightarrow{A}\cdot\overrightarrow{B}}{|A|\cdot|B|}=\cos<A,B>\)
當\(A\parallel B,\sin<A,B>=\sin(90^{\circ})\)
\(\overrightarrow{A}\times\overrightarrow{B}=|A|\cdot|B|\cdot\sin<A,B>=0\)
db ag(vc x,vc y){
return acos(dt(a,b)/di(a)/di(b));
}
求一個向量的法向量:
如果向量的座標式為\((x,y)\),則\((-y,x)\)與該向量垂直,除以模就是法向量。
vc qf(vc x){
db v=di(x);
return (vc){-x.y/v,x.x/v};
}
向量的旋轉:
根據平面向量基本定理,以\((1,0),(0,1)\)為基底,有唯一的方式表示出要旋轉的向量\((x,y)=(1,0)*x+(0,1)*y\)
分別旋轉兩個向量再加起來,得到\(x*(\cos\theta,\sin\theta)+y*(-\sin\theta,\cos\theta)\)
vc rot(vc x,double y){
db v1=sin(y),v2=cos(y);
return (vc){x.x*v2-y.x*v1,x.x*v1+x.y*v2};
}
點到直線的距離
利用叉積求出面積,得到了兩個向量構成的有向平行四邊形的距離。
然後除以平行四邊形的底邊長,得到平行四邊形的高再絕對值即點到直線的距離
現在要求的是\(p\)到\(a,b\)連成的直線的距離。
double di(pt p,pt a,pt b){
vc v1=p-a,v2=b-a;
return fabs(cr(v1,v2))/di(v2);
}
點到線段的距離
和上一問不同的是,這道題不能直接作直線的垂直。
如果作垂直,可能不線上段上。
解決方法是:當平行四邊形的高在區域外,答案取到兩個端點的距離的最小值。
假設\(\overrightarrow{b}-\overrightarrow{a}=v1,\overrightarrow{p}-\overrightarrow{a}=v2,\overrightarrow{p}-\overrightarrow{b}=v3\)
當\(v2\)和\(v1\)的夾角>90度時,則p的投影在直線外面
同理,當\(v3,v1\)的夾角<90度時,則p的投影也在直線外面。
使用這個結論判定即可。
double dts(pl p,pl a,pl b){
if(a==b)return di(p-a);
vc v1=b-a,v2=p-a,v3=p-b;
if(dc(dt(v1,v2))<0)return di(v2);
else if(dc(dt(v1,v3))>0)return di(v3);
return fabs(cr(v1,v2))/di(v1);
}
如果線段\(ab\)和\(cd\)相交,那麼\(a,b\)到直線\(cd\)作垂線有交點,或者\(c,d\)到直線\(ab\)作垂線有交點
使用剛才求點到線段的距離的方法判定即可。
判定兩個線段是否相交
快速跨立實驗:判定一條線段的兩個點是否在另外一條線段的兩邊。
要做2次。
bool xj(pt a,pt b,pt c,pt d){
double v1=cr(b-a,c-a),v2=cr(b-a,d-a),v3=cr(d-c,a-c),v4=cr(d-c,b-c);
return dc(v1)*dc(v2)<0&&dc(v3)*dc(v4)<0;
}
求一個點是否在多邊形上可以使用角度/射線法。
具體可以看uoj白鴿。
求兩條直線的交點:
盜了圖。
pt its(pt p,vc v,pt q,vc w){
vc t=p-q;
double g=cr(w,t)/cr(v,w);
return p+v*g;
}
求兩條線段的交點只需要先判定是否相交,再求兩條直線的交點即可。
求一個多邊形的面積可以三角剖分,對每個三角形的有向面積加起來最後再/2。
這樣子做,外邊的部分恰好被抵消了。
最後要取絕對值。
double ar(pt *a,int n){
double ans=0;
for(int i=1;i<n-1;i++)
ans+=cr(a[i]-a[0],a[i+1]-a[0]);
return ans/2;
}
完整程式碼:
#include<bits/stdc++.h>
using namespace std;
#define eps 1e-8
struct pt{
double x,y;
};
int dc(double x){
if(fabs(x)<eps)return 0;
return 1;
}
typedef vc pt;
pt operator +(pt x,vc y){
return (pt){x.x+y.x,x.y+y.y};
}
pt operator -(pt x,vc y){
return (pt){x.x-y.x,x.y-y.y};
}
vc operator +(vc x,vc y){
return (vc){x.x+y.x,x.y+y.y};
}
vc operator -(vc x,vc y){
return (vc){x.x-y.x,x.y-y.y};
}
vc operator *(vc x,double y){
return (vc){x.x*y,x.y*y};
}
vc operator /(vc x,double y){
return (vc){x.x/y,x.y/y};
}
vc operator ==(vc x,vc y){
return x.x==y.x&&x.y==y.y;
}
pt operator ==(pt x,pt y){
return x.x==y.x&&x.y==y.y;
}
double di(pt x){
return sqrt(x.x*x.x+x.y*x.y);
}
int dt(vc x,vc y){
return x.x*y.x+x.y*y.y;
}
int cr(vc x,vc y){
return x.x*y.y-x.y*y.x;
}
db ag(vc x,vc y){
return acos(dt(a,b)/di(a)/di(b));
}
vc qf(vc x){
db v=di(x);
return (vc){-x.y/v,x.x/v};
}
vc rot(vc x,double y){
db v1=sin(y),v2=cos(y);
return (vc){x.x*v2-y.x*v1,x.x*v1+x.y*v2};
}
double dtl(pt p,pt a,pt b){
vc v1=p-a,v2=b-a;
return fabs(cr(v1,v2))/di(v2);
}
double dts(pl p,pl a,pl b){
if(a==b)return di(p-a);
vc v1=b-a,v2=p-a,v3=p-b;
if(dc(dt(v1,v2))<0)return di(v2);
else if(dc(dt(v1,v3))>0)return di(v3);
return fabs(cr(v1,v2))/di(v1);
}
bool xj(pt a,pt b,pt c,pt d){
double v1=cr(b-a,c-a),v2=cr(b-a,d-a),v3=cr(d-c,a-c),v4=cr(d-c,b-c);
return dc(v1)*dc(v2)<0&&dc(v3)*dc(v4)<0;
}
pt its(pt p,vc v,pt q,vc w){
vc t=p-q;
double g=cr(w,t)/cr(v,w);
return p+v*g;
}
double ar(pt *a,int n){
double ans=0;
for(int i=1;i<n-1;i++)
ans+=cr(a[i]-a[0],a[i+1]-a[0]);
return ans/2;
}
未完待續