1. 程式人生 > >【learning】凸包

【learning】凸包

n) 不能 dot over 思路 定義 原因 中間 平面

吐槽

計算幾何這種東西qwq一開始真的覺得惡心qwq(主要是總覺得為啥畫圖那麽直觀的東西非要寫一大堆式子來求qwq真的難受qwq)

但其實靜下心來學習的話感覺還是很妙的ovo題目思考起來也十分好玩ovo

正題

學習凸包需要一點前置技能:極角,向量點積,向量叉積

1.極角

? 在平面上取一定點\(O\),從\(O\)引一條水平射線\(Ox\),規定方向自左至右,再選定一個長度單位並規定角旋轉的正方向(通常取逆時針方向),這樣就構成了一個極坐標系,其中\(O\)叫做極點,射線\(Ox\)叫做極軸

? 在極坐標系中,平面上任意一點到極點的連線和極軸的夾角叫做極角

2.向量點積

? 一般寫作:\(\vec a \bullet \vec b\)

,幾何定義為
\[ \vec a \bullet \vec b = |\vec a||\vec b|\cdot cos\theta \]
? 其中\(\theta\)為兩個向量的夾角,該定義只針對二維和三維(其他的。。暫時不需要用到所以就先不寫了qwq),其實可以簡單理解為第一個向量投影到第二個向量上的長度(就是作垂直啦其實),所以其實點積是個標量

? 特別的,如果是在二維平面中,兩個向量\(\vec a = (x_1,y_1)\)\(\vec b = (x_2,y_2)\)的點積可以表示為
\[ \vec a\bullet\vec b = x_1x_2 +y_1y_2 \]
? 點積滿足交換律

3.向量叉積

? 一般寫作:\(\vec a × \vec b\),定義為
\[ \vec a × \vec b= ab \cdot sin \theta \]
? 其中\(\theta\)為兩個向量的夾角,簡單理解的話就是兩個向量圍成的平行四邊形的面積(但是是有正負的)

? 與點積不同的是,叉積是個矢量,帶方向的

? 在二維平面中,兩個向量\(\vec a = (x_1,y_1)\)\(\vec b = (x_2,y_2)\)的叉積可以表示為
\[ \vec a × \vec b = x_1y_2-x_2y_1 \]
? 叉積不滿足交換律,交換後的結果方向與原來相反(其實就是多個負號)

點積和叉積的幾何意義十分好用,在題目中的應用很廣泛,用起來也是很靈活的(具體還是要看題目說啦qwq不然都是空話)

好的然後我們正式開始講凸包qwq

graham算法

? 這個算法的思路十分簡單粗暴,就是首先找到最下面最左邊的那個點(先按照\(y\)排序有相同的再看\(x\)),然後把這個點定為極點,把剩下的點按照極角排序,極角相同就按照到極點的距離排,然後用一個棧記錄凸包上的點,O(n)掃一遍,如果說當前的點在目前圍成的凸包外,那麽就說明棧頂的點應該在凸包內,一直退棧直到滿足當前點在凸包上為止,再將當前點進棧,然後再看下一個點

? 最後棧中的就是所有凸包上面的點啦ovo

? 現在的問題就是怎麽求極角以及判斷當前點的位置

? 這裏有一個非常好的結論,\(\vec a\)如果滿足\(\vec a × \vec b > 0\),那麽說明\(\vec b\)\(\vec a\)的逆時針方向,反之就是在順時針

? 那麽極角其實我們可以直接用叉積來判斷就好了,如果說兩個向量的叉積\(>0\),那麽前者的極角一定小於後者,如果等於0說明在同一條直線上,就用距離判斷

? 同樣的我們就可以快速判斷一個點是否在凸包外了

? (這裏是逆時針掃的)我們記當前棧頂的點為\(s_0\),棧頂的下一個點為\(s_1\),當前點為\(x\)

? 畫一下圖就能發現,\(x\)在當前的凸包上當且僅當\(\overrightarrow{s_1x}\)\(\overrightarrow{s_1s_0}\)的逆時針方向,也就是兩個向量的叉積要大於0,這樣就能快速判斷\(x\)的位置了

? 然後就這麽一路掃一路操作就能得到凸包啦ovo

? 如果說要分別求得上凸包和下凸包,那就一開始按照\(x\)排序相同再按照\(y\)排序,然後掃的時候用兩個棧分別記錄上下凸包,判斷條件相反(一個是叉積<=0就退棧,一個是>=0就退棧)就ok了

? 值得註意的是,這裏的等號不能去掉,否則遇到多個坐標相同的點會出問題

旋轉卡殼

? 提到凸包就不能不提到這個算法啦

? 以經典的求凸包直徑做例子,首先講一下大體思路

? 首先可以發現,一個點與凸包中其他點的連線長度是一個單峰函數,所以如果要枚舉的話一旦發現答案開始遞減了就可以停止了,普通的暴力在枚舉下一個點的答案的時候,會又從最靠近的那個點開始掃,但其實我們畫個圖觀察一下會發現其實不用,只要把上一個點(記作\(x\))開始遞減的那個點(記作\(i\)) 當做起點開始掃就可以了。原因的話是顯然在\(i\)之前的那些點到\(x\)的距離會比到當前點的距離要長,所以根本就沒有掃的必要了(只會直觀證明qwq嚴格證明的話。。。不會qwq)

? 這樣一來我們就可以線性處理出直徑啦ovo

? 對於其他的問題,思路其實差不多,主要還是利用好單峰函數這個特點避免掉不必要的操作

動態加點

? 如果說是動態加點的話,維護起來差不多,一點小改動

? 將要加進來的點記作\(x\),我們先在當前的凸包上找到\(x\)前面和後面的兩個點,形象理解就是能將\(x\)夾在中間的兩個點(額。。其實實現起來用lower_bound就好了),然後從這裏開始重新模擬一遍graham中退棧進棧的過程就好了

應用

? 除了各種奇妙幾何題之外,凸包還有一個很大的用處就是優化,最典型的例子就是斜率優化,其實就是維護一個下凸包

? 額不過這種東西的話。。好像還是要具體題目具體分析qwq反正總的應該就是轉成點的形式然後亂搞就好了嗯qwq(好吧其實這種題目的重點應該都是推式子吧哈哈哈。。qwq)

【learning】凸包