1. 程式人生 > 其它 >凸包學習筆記

凸包學習筆記

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;
}

完結撒花~

你覺得對你有幫助的話,請不要吝嗇您手中的贊

如果有講得不清楚的地方,或講錯的地方,歡迎在討論區打臉

學完了以後不要忘了做這些題哦