1. 程式人生 > 實用技巧 >計算幾何 學習筆記

計算幾何 學習筆記

部分參考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;
}

未完待續