計算幾何學習筆記
以下的圖都是盜來的。
向量
向量是既有大小又有方向的量,可以用一個帶有箭頭的線段表示,表示為 \(\vec{a}\),由於具有平移不變性,所以可以將起點平移到原點,用一個座標 \(( x , y)\) 來表示一個向量。
向量的模就是向量的長度,對於 \(\vec{a}=( x , y)\) ,表示為 \(|\vec{a}|=\sqrt{x^2+y^2}\) 。
向量的加減滿足交換律,對於 \(\vec{a}=( x_0 , y_0)\ ,\ \vec{b}=( x_1 , y_1)\) , \(\vec{a}\pm \vec{b}=( x_0\pm x_1 , y_0\pm y_1)\) 。
向量的數量積,也稱點積,是一個實數,幾何意義為一個向量在另一個向量上的投影再乘上第二個向量的模長,計算式為
\(\vec{a}\cdot\vec{b}=|\vec{a}||\vec{b}|\cos\theta(\theta=\left<\vec{a},\vec{b}\right>)\)
由點積可以判斷並計算兩個向量的夾角,大於 \(90^\circ\) 時點積為正,等於 \(90^\circ\) 時點積為零,小於 \(90^\circ\) 時點積為負。
向量的外積,也稱叉積,是一個向量,幾何意義是兩向量由平行四邊形法則圍成的面積,計算式為 \(\vec{a}\times\vec{b}=|\vec{a}||\vec{b}|\sin\theta\) ,對於 \(\vec{a}=(x_1,y_1)\ ,\ \vec{b}=(x_2,y_2)\ ,\ \vec{a}\times\vec{b}=x_1y_2-x_2y_1\)
向量的叉積的正負可以理解為從 \(a\) 逆時針轉向 \(b\) 的夾角的範圍,當夾角為平角則為正,否則為負。
所以可以通過叉積的計算來判斷兩個向量的旋轉關係,由於其幾何意義以及正負的性質,也可以應用於求多邊形的面積,做法是算出所有邊的兩端的向量的叉積再除以二。
struct Vector{ int x,y; Vector friend operator -(Vector a,Vector b){ return {a.x-b.x,a.y-b.y}; } Vector friend operator +(Vector a,Vector b){ return {a.x+b.x,a.y+b.y}; } Vector friend operator *(Vector a,int b){ return {a.x*b,a.y*b}; } int friend operator *(Vector a,Vector b){ return a.x*b.y-a.y*b.x; }//叉積 int friend operator ^(Vector a,Vector b){ return a.x*b.x+a.y*b.y; }//點積 };
線段交點
直接盜圖和別人的解釋吧。
因為 \(\overrightarrow{AO}=\frac{|AO|}{|AB|}\cdot \overrightarrow{AB}=\frac{|AO|}{|AO|+|BO|}\cdot \overrightarrow{AB}\) ,令原點為 \(O^{'}\),則 \(\overrightarrow{O'O}=\overrightarrow{O'A}+\overrightarrow{AO}\) , \(\overrightarrow{O'O}\) 的座標即為 \(O\) 的座標。
Vector cross(Line a,Line b){
return b.pre+b.v*(((b.pre-a.pre)*a.v)/(a.v*b.v));
}
凸包
有一個比較普遍的做法,就是將點按 \(x\) 排序,然後從最左邊的一個點出發,不斷將點 \(A\) 加入棧,設 \(B,C\) 為棧頂的兩個點,如果 \(BA\) 在 \(BC\) 的下面,那麼 \(C\) 一定不是凸包上的點,就可以將 \(C\) 彈出棧。
對於判斷,可以通過向量的叉積實現,如果 \(BA\) 在 \(BC\) 的下面,那麼從 \(BC\) 轉向 \(BA\) 的夾角一定不是平角, \(\vec{BC}\times\vec{BA}\le 0\) 。
int dis(Vector a){ return a.x*a.x+a.y*a.y; }
bool cmp(Vector a,Vector b){
int x=(a-d[1])*(b-d[1]);
return x>0||(x==0&&dis(a-d[1])<dis(b-d[1]));
}
int k=1,top=2;
void Get(){
for(int i=2;i<=n;i++) if(d[i].y<d[k].y||(d[i].y==d[k].y&&d[i].x<d[k].x)) k=i;
swap(d[1],d[k]);sort(d+2,d+n+1,cmp);s[1]=d[1],s[2]=d[2];
for(int i=3;i<=n;i++){
while(top>=2&&(s[top]-s[top-1])*(d[i]-s[top-1])<=0) top--;
s[++top]=d[i];
}
}
旋轉卡殼
先了解對踵點,如果過凸多邊形上兩點作一對平行線,使得整個多邊形都在這兩條線之間,那麼這兩個點被稱為一對對踵點。
再瞭解凸多邊形的直徑,就是凸包上距離最遠的兩個點的距離,通過反證法可以發現這樣的點對一定是對踵點。
至於旋轉卡殼。。。
可以發現平行線同時接觸的兩個點就是對踵點,所以旋轉卡殼就是在列舉對踵點。
由於是凸多邊形,一個點要求另外一個點組成對踵點可以通過三分法,單個查詢 \(\mathcal{o(\log n)}\) ,總複雜度 \(\mathcal{o(n\log n)}\) 。
不過可以發現,對踵點不會回退,畫個圖大概就可以理解,所以可以使用雙指標來遍歷凸包,判斷可以通過計算三角形的面積,由於對踵點的定義是關於平行線的,所以對於凸包上的點的一條邊,另外一個對踵點一定是所有點中到那條邊的垂直距離最大的,至於三角形面積可以通過向量的叉積來計算。
int GetMax(){//求直徑
int res=0;
if(top==2) return dis(s[1]-s[2]);
s[++top]=s[1];
for(int i=0,j=2;i<top;i++){
while((s[i]-s[j])*(s[i+1]-s[j])<(s[i]-s[j+1])*(s[i+1]-s[j+1])) j=(j+1==top)?1:j+1;
res=max(res,max(dis(s[i]-s[j]),dis(s[i+1]-s[j])));
}
return res;
}
半平面交
半平面的定義是一條直線和直線的一側,是一個點集,包含直線時稱閉半平面,不包含時稱開半平面,在計算幾何中用向量表示,整個題統一以向量的左側或右側為半平面。
半平面交就是多個半平面交的交集。
半平面交的求解需要先極角排序,通過C++自帶的函式 atan2(double y,double x)
可以返回 \(θ=arctan(\frac{y}{x})\) ,如果用向量來表示邊的話大概就是向量的斜率。
通過這個來排序,然後維護單調佇列,然後就咕咕咕了。。。