計算幾何
zrf 評價:不用學得特別好。
世紀難題:誰在卡(雙關)。
學習目標
學會快速正確地打出暴力,防止精度爆炸。
計算幾何的基本概念
向量 yyds !
有三種表示:
-
點對表示
-
復數表示
兩個復數相乘的時候,輻角相加,模長相乘。
如果我們有一個向量長度是 \(1\) ,我們就可以利用這個向量來旋轉。逆時針旋轉 \(90°\) 的操作可以特意編一個函數來加快運行速度。
如果我們有一個向量的輻角是 \(0\) ,我們就可以利用這個向量來縮放。
范數(?)
就是長度的平方。開根號容易出現精度問題,所以可以先不開。
程序設計的藝術
OOP
利用結構體和成員函數。對象內部的數據讓對象自己操作。
FP
將邏輯上的純函數實現為純函數。
純函數:與全局無關的函數,只與傳參有關。
zrf:日常找不到鼠標。
功能細分
即使只被調用一次,也盡量寫成單獨的函數,長函數名可充當注釋。
拼音長函數名。
關於浮點誤差
原因:double
存在有效位數的限制,導致丟失一定的精度。
計算是不存在大的誤差的,而存儲存在誤差。
zrf 評論:微積分這個東西學得好也沒有用,只能應付大學生的考試。
eps
的選取:不要濫用,精度夠用就行。具體題目具體分析。
eps
的正確性:平衡正確性和不正確性。考慮兩個值的誤差的分布是接近 \(0\) ,所以我們以概率的角度來考慮不正確性是很低的。(經驗主義結論)
使用范圍
-
在比較大小的時候可以使用
eps
。 -
在一些函數的使用過程中也可以使用
eps
。 -
二分中不能使用
eps
。while(r-l>eps)
❌,超大數的低位消失后可能會導致上式死循環或直接彈出。考慮直接枚舉二分次數 ⭕ 。
二分的
check
是不抗擾動的,但是最后的值是不會出現大的問題的,因為誤差只是平移check
函數,盡量不使用eps
。
真實作用:處理一些存在幾個位置的導數絕對值十分大的函數時利用 eps
,即函數在這個位置不抗擾動的時候。同時需要滿足自變量的誤差分布集中在正確值附近。
zrf :我罵我自己。
如何判斷一個數是 nan
:(nan!=nan)=true
。
zrf 評論:明哥牛逼!
例題
求兩個向量夾角的弧度,精度要求 \(10^{-11}\) 。
題解
sol 1 :直接求輻角,相減。
sol 2:轉換成復數相除,然后求輻角。
例題
求兩個空間向量的夾角的弧度,精度要求 \(10^{-11}\) 。
題解
sol 1:找出平面,轉換成平面問題。
sol 2:考慮使用空間上的點積算余弦,叉積算正弦,然后分類討論,擇優選取精度高的一個函數。
一些常見的元素
點:用向量表示。
直線:用點加向量表示。
圓:圓心加半徑。
兩條線段是否相交:判斷兩次跨越。
點到直線的垂足:直接點積求投影長度。
角平分線:歸一化后直接加起來,可能需要特判平角。
兩條直線的交點:
- 面積法求出點在直線上的比例。
- 代數法列出等式,利用行列式解方程組,行列式是叉積的代數意義(?)。
圓和直線的交點:直接解三角形,其中可以有優化。
兩圓的交點:解三角形求角,旋轉向量並縮放。
兩圓的公切線:
- 外公切線:將圓壓縮,變成點到圓的切線。
- 內公切線:考慮一個壓縮一個擴張,還是變成點到圓的切線。
tricks
極限情況
- 考慮極限情況。
- 考慮二分 or 三分。
最小圓覆蓋
最小圓應該是被兩或三個點卡住的,枚舉即可。
corner case:只有一個點。
Red And Blue Points
考慮每一條線可以被看成被兩個點一左一右卡住,所以我們就可以枚舉出所有可能的直線。
但是需要考慮共線的情況,盡量不通過旋轉角度的方法來實現。
zrf:只要你考慮到了,隨手拿出一種方案都是對的。
有兩種解決方案,
- 直接不考慮。
- 轉換成一個合法解。
corner case:存在只有一邊有點。
極角掃描
Red And Blue Points (plus)
自由度:若 \(x\) 個有序實數可以表示有限個元素,最小的 \(x\) 就是該元素的自由度。
一個方程可以減少一個自由度,一個未知數可以將自由度加一。自由度為 \(0\) 的元素就是有限確定的。
有向直線的自由度為 \(2\) ,極角和截距。
掃描:可以用來求最優的自由度為 \(1\) 的元素。
- 計算起始狀態。
- 狀態緩慢(?)轉移到終止狀態。
- 考慮轉移的過程是由事件構成的。事件即在掃描過程中對狀態產生影響的東西,是有限的。
- 我們將事件按發生的事件排序,依次處理即可。
就是類似於掃描線的思路。
枚舉一個點,考慮剩下的點極角排序(用叉積來實現),這樣就可以快速維護直線兩側的紅藍點個數,就直接掃描?
對於同時發生的事件,我們只需要經過一些處理讓它以我們希望的方式發生就行了,或者是直接同時處理一波。
在幾何意義上,
等價於
,但
的最大優勢是可以正確處理
而
的情況,而不必進行會引發除零異常的
操作。
考慮我們一開始的初始狀態可以邏輯上偏移一個小角度,使得一開始的狀態上沒有事件。
極角排序的 trick
我們考慮將 \(x\) 軸旋轉一個小角度,對於在直線上方的標為 \(0\) ,否則為 \(1\) 。然后對於不同標號的,比較標號;對於同標號的,比較叉積的正負。
無題
給定 \(n\) 個點,問最多有幾個點共線。
就是上面的題一樣的搞法?
Pionek
首先我們考慮極角排序之后相當於一個凸包了,然后單峰性和對於左端點最優右端點也是單調的。
然后我們就做好了。
簡單多邊形
多邊形概念:
- 簡單多邊形:沒有邊相交的多邊形。
- 凸多邊形:凸多邊形。
如何判斷一個點在簡單多邊形的內部。
轉角法:判斷向量掃過的度數和(有向的),在內部是 \(2\pi\) ,不再內部是 \(0\) 。
corner case:不能判斷在多邊形上,但是可以直接判斷是否在多邊形上。
折線法:過點引出一個直線,判斷穿過了邊界幾次。
求面積:叉積求個和。
Get Out
先將有交的圓的圓心相連。
如果不合法,那么一定存在一個多邊形將原點包住。
利用轉角法,我們可以轉變成判斷是否存在一個環的權值是大於 \(0\) 的。
也可以利用射線法,考慮拆點,每一個點奇偶分開連邊。
凸包
動態凸包
\(\text{set}\) 維護即可。
旋轉卡殼
通過一對平行線卡住這個凸包,同時更新答案。這個可以通過進行斜率的排序來實現,或者雙指針來實現。
兩個凸包上的最遠點對
我們可以通過枚舉一個向量,一個點是這個向量正方向最遠的點,一個點是這個向量負方向最遠的點。
兩個凸包上的最近點對
有很多的 \(\text{corner case}\) 。
兩個凸包的公切線
首先可能存在四條。
我們考慮用兩條斜率相同的直線去切這兩個凸包,我們考慮到如果兩個直線在一瞬間成為了公切線,那么他們此時的距離就會成為 \(0\) 。我們考慮在每一次的事件的過程中,如果兩條直線的距離跨過了零點,那么說明在兩次事件之間存在公切線。
半平面交
類似構建凸包的思路,但是需要特判首尾。
例 1 :Average Convex Hull
隨機刪除一個點,求剩余 \(n-1\) 個點形成的凸包的頂點數的期望。
我們可以求出兩層的凸包,由於只刪除一個點,所以最多只會露出第二層一段區間。
還有一種思路就是,我們刪除一個點之后,我們只需要重新求出一個三角形內的凸包,由於發現一個點最多只會存在於 \(O(1)\) 個三角形中。
但是發現三角形的凸包處理起來很麻煩,我們就考慮重建兩個相鄰點區間內的所有的點。證明每一個點最多只會存在於四個區間內部。
平面最近點對
\(O(n^2)\) 的好方法。
我們先隨機一個角度並旋轉每一個點。
然后嚴格按照 \(x\) 從小到大的順序更新答案,如果遇到 \(x\) 的距離大於當前答案了,就直接 \(\text{break}\) 。
數值積分
積分
求一個函數下方到 \(x\) 軸圍成的有向面積。
我們考慮如果將 \([a,b]\) 細分成若干段長度為 \(\Delta x\) 的段,每一個小區間的函數值乘上長度的累和就是積分。
符號記為:
積分是求導的逆運算,其中 \(c\) 是常數。
考慮一開始的式子:
一元積分在集合方面的應用
函數圖像下方的面積:上面的公式來搞就行了。
一般圖形的面積:上積分減下積分(實際上不需要分開考慮,詳見格林公式?)。
求一條曲線的長度:
求旋轉體的體積:
藝術大師 zrf !
求旋轉體的表面積:使用圓台的側面積來近似。
總結:在使用積分的時候需要考慮到我們近似的圖形對於答案的影響,如果影響的極限是 \(0\) 就可以使用這樣的近似。
圓的面積並(BZOJ 2178)
給出 \(n\) 個圓,\(n\le 2000\),要求誤差在 \(10^{-9}\) 以內。
先通過暴力判斷一個圓的哪些部分是邊界的一部分。
然后我們通過一些三角函數和面積計算公式暴力計算就行了?
圓弧的精確積分
弓形加梯形,就是上面的方法。
Simpson 積分公式
有一個不確定的但是可以 \(O(1)\) 計算的函數 \(f(x)\) ,求它在 \([a,b]\) 上的積分。
既沒有時間保證,又沒有正確性保證的算法,這是一個經驗性算法。
我們用一個二次函數 \(g(x)\) 擬合這個函數?令 \(c=\frac{a+b}{2}\) ,
遞歸使用上面的公式,同時將誤差分配給兩邊,就是自適應辛普森。
復合區域的面積計算
簡單區域:一些可以直接運算的幾何圖形。
復合區域:簡單區域進行邏輯運算后得到的區域。
方法:
- 求出邊界。
- 求積分的和。
我們考慮還是對於每一個簡單區域邊界的兩邊是否存在於復合區域內。
這個可以利用掃描的方法來解決。
表達式樹:維護計算過程的一棵樹。
Airport Construction (WF 2017)
給定一個簡單多邊形,求一條最長的線段,使得線段上的每一個點都位於多邊形內部。
考慮枚舉兩個點,做出過這兩個點的直線,那么如果這條直線合法,那么答案就是這條直線與這個多邊形最外側的交的距離。
我們考慮判斷這個直線是否合法,在不考慮貢獻的情況,如果這個直線能將整個多邊形划成兩塊,說明直線是合法的。
Cherry Orchard (BSUIR final 2015)
咕咕咕。