凸包——Graham掃描法

廢話

這是一個用來求出凸包的算法。

前置芝士——叉積,知道了這個之後,就可以判斷兩個向量之間的旋轉關係。

舉個例子,假如 a\vec a 順時針旋轉 180°180\degree 以內能得到 b\vec b,那麼稱 b\vec ba\vec a 的右邊,否則在左邊或共線。

正題

這個Graham掃描法其實很好理解,主要有下面幾步:

  1. 找到一個縱座標最小,在此基礎上橫座標最小的點,可以知道這個點肯定在凸包上,以這個點作爲原點重新建立座標系。
  2. 將所有點按極角從小到大排序,極角就是這個點到原點這條線段與 xx 軸的夾角,極角相同的將離原點更近的放前面
  3. 維護一個棧,記錄當前凸包上的點,然後將每個點依次嘗試放入。

重點就是步驟 33,如何進行這個嘗試。

可以發現,按極角排序後,其實相當於將所有點按逆時針排序。凸包有一個性質,即對於凸包上任意兩個相鄰的點 a,ba,b,所有點都在直線 abab 的左邊,或者就在線段 abab 上,這意味着,abab 的右邊是不能有點的。

於是,設 a,ba,b 爲此時棧頂的兩個點,每次插入一個新點 xx 時,如果 xxabab 的右邊或在直線 abab 上並且不在線段 abab 上,那麼 bb 肯定就不是凸包上的點,將 bb 踢掉,重複這個過程直到只剩一個點或 xxabab 左邊了,然後將點 xx 加入棧中。

畫個圖,就是這樣的(xxabab 左邊):
在這裏插入圖片描述
模板題傳送門

代碼如下:

#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
#define maxn 100010
#define db double
#define inf 999999999

int n,m=0,t=0;
struct point{
	db x,y; point(db xx=0,db yy=0):x(xx),y(yy){}
	point operator -(const point &b){return point(x-b.x,y-b.y);}
	db operator *(const point &b){return x*b.y-y*b.x;}
};
point a[maxn],b[maxn],S,zhan[maxn];
db dis(point x,point y){return sqrt((x.x-y.x)*(x.x-y.x)+(x.y-y.y)*(x.y-y.y));}
bool cmp(point x,point y){return x*y>0||(x*y==0&&dis(S,x)<dis(S,y));}

int main()
{
	
	scanf("%d",&n);S.y=inf;
	for(int i=1;i<=n;i++){
		scanf("%lf %lf",&a[i].x,&a[i].y);
		if(a[i].y<S.y||(S.y==a[i].y&&a[i].x<S.x)){
			if(S.y!=inf)b[++m]=S;
			S=a[i];
		} else b[++m]=a[i];
	}
	for(int i=1;i<=m;i++)b[i].x-=S.x,b[i].y-=S.y;
	sort(b+1,b+m+1,cmp);
	zhan[++t]=S=point(0,0); zhan[++t]=b[1];
	for(int i=2;i<=m;i++){
		while(t>1&&(zhan[t]-zhan[t-1])*(b[i]-zhan[t-1])<=0)t--;
		zhan[++t]=b[i];
	}
	db ans=dis(zhan[t],zhan[1]);
	for(int i=1;i<t;i++)ans+=dis(zhan[i],zhan[i+1]);
	printf("%.2lf",ans);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章