凸包學習筆記
2022/3/11 Update:學會了叉積,來改一下以前瞎逼逼的,不知道是什麼東西的東西
以這道為例題
簡化題意:給出 $n$ 個點的座標,問最小的多邊形的周長,滿足 $n$ 個點都被他包含(邊上也算)
我直接開講 $\texttt{Graham}$ 吧
現在我們有一張圖:
開始給出的點沒有順序,我們首先需要找一種處理的順序
我們從最下面的點開始好了
容易想到一種排序方式,把最下面的點與其他點連邊,就像這樣:
圖略醜誤噴
然後從右往左處理每個點
排序部分的程式碼實現:
bool cmp(node p1,node p2){ db tmp=check(p[1],p1,p[1],p2); if(tmp>0 ||(tmp==0 && dis(p[0],p1)<dis(p[0],p2))) return 1; return 0; }
接下來就是具體如何處理了
二話不說先放圖:
第一條連邊,沒什麼好說的
第二條,夾角超過180度,顯然符合
哦我說的是這個角
好,問題來了現在相鄰的邊的夾角<180度了,此時顯然有一種更優的情況:
褐色的太遜了,被淘汰了
接下來沒什麼事了
凸包畫(吐)完辣!
於是得出這部分的程式碼:
tb[1]=p[1];
int cnt=1;
for(int i=2;i<=n;i++){
while(cnt>1&&check(tb[cnt-1],tb[cnt],tb[cnt],p[i])<=0) cnt--;
tb[++cnt]=p[i];
}
哦對了,兩段程式碼裡都用到的 $\operatorname{check}$ 函式什麼呢
Upd:那時我好遜,為什麼取名叫$check$不叫$cross$
db check(node a1,node a2,node b1,node b2){
return (a2.x-a1.x)*(b2.y-b1.y)-(b2.x-b1.x)*(a2.y-a1.y);
}
還是先貼程式碼,這個柿子正負值表示的含義(可以死記)可以推
斜率想必大家都知道,
$k_{\overrightarrow{a_1a_2}}=({a_2}_y-{a_1}_y)/({a_2}_x-{a_1}_x)$
$k_{\overrightarrow{b_1b_2}}=({b_2}_y-{b_1}_y)/({b_2}_x-{b_1}_x)$
在 $sort$ 的 $check$ 中,$check$用於比較兩個點分別與$p_1$ 連起來後線段的先後順序
於是有這兩種情況:
於是,當$({a_2}_x-{a_1}_x)$
和
$({b_2}_x-{b_1}_x)>0$
時
$k_{\overrightarrow{a_1a_2}}-k_{\overrightarrow{b_1b_2}}$
$=({a_2}_y-{a_1}_y)/({a_2}_x-{a_1}_x)-({b_2}_y-{b_1}_y)/({b_2}_x-{b_1}_x)$
與
$({a_2}_y-{a_1}_y)({b_2}_x-{b_1}_x)-({b_2}_y-{b_1}_y)({a_2}_x-{a_1}_x)$
同號
即$k_{\overrightarrow{b_1b_2}}-k_{\overrightarrow{a_1a_2}}$
與
$({b_2}_y-{b_1}_y)({a_2}_x-{a_1}_x)-({a_2}_y-{a_1}_y)({b_2}_x-{b_1}_x)$
同號,
於是這個柿子 $>0$ 的意義是 $k_{\overrightarrow{b_1b_2}}-k_{\overrightarrow{a_1a_2}}>0$
即後者的斜率比前者大,於是這部分排序後順序就該是這樣
啊哈,排對了
對於 $2$ 的話
$({b_2}_y-{b_1}_y)({a_2}_x-{a_1}_x)-({a_2}_y-{a_1}_y)({b_2}_x-{b_1}_x)$
中
$({b_2}_y-{b_1}_y)*({a_2}_x-{a_1}_x)>0$
$({a_2}_y-{a_1}_y)*({b_2}_x-{b_1}_x)<0$
於是
$({b_2}_y-{b_1}_y)({a_2}_x-{a_1}_x)-({a_2}_y-{a_1}_y)({b_2}_x-{b_1}_x)>0$
按照這個順序:
還有一種情況剛才忘了
也易證,順序是對的
以上是排序時 $\operatorname{check}$ 的用法
接下來是取凸包點時 $\operatorname{check}$ 的用法
將會有以下幾種情況:
是符合條件的, $\operatorname{check}$ 值 $>0$
讀者自證不難
Upd:不就是我那時候不會嗎,根本不用分討,用叉積的性質解決一切問題 Click Here
剩下的,不符合的刪乾淨,最後就能得到完整的凸包,程式碼上邊已經放過了
做到這裡,這道題剩下部分小問題了吧,求每兩個相鄰點的歐氏距離,相加即為周長
這裡放一下完整的程式碼:
#include<bits/stdc++.h>
#define db double
using namespace std;
const int M=1e5+7;
int n; db ans;
struct node{
db x,y;
}p[M],tb[M];
db check(node a1,node a2,node b1,node b2){
return (a2.x-a1.x)*(b2.y-b1.y)-(b2.x-b1.x)*(a2.y-a1.y);
//<0表右轉 =0表共線 >0表左轉
}
db dis(node p1,node p2){
return sqrt((p2.y-p1.y)*(p2.y-p1.y)+(p2.x-p1.x)*(p2.x-p1.x));
}
bool cmp(node p1,node p2){
db tmp=check(p[1],p1,p[1],p2);
if(tmp>0 ||(tmp==0 && dis(p[0],p1)<dis(p[0],p2))) return 1;
return 0;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lf%lf",&p[i].x,&p[i].y);
if(i!=1&&(p[i].y<p[1].y ||(p[i].y==p[1].y && p[i].x<p[1].x)))
swap(p[1],p[i]);
}
sort(p+2,p+1+n,cmp),tb[1]=p[1];
int cnt=1;
for(int i=2;i<=n;i++){
while(cnt>1&&check(tb[cnt-1],tb[cnt],tb[cnt],p[i])<=0) cnt--;
tb[++cnt]=p[i];
}
for(int i=1;i<=cnt;i++) ans+=dis(tb[i],tb[i%cnt+1]);
printf("%.2lf\n",ans);
return 0;
}
完結撒花~
你覺得對你有幫助的話,請不要吝嗇您手中的贊
如果有講得不清楚的地方,或講錯的地方,歡迎在討論區打臉