廢話
這是一個用來求出凸包的算法。
前置芝士——叉積,知道了這個之後,就可以判斷兩個向量之間的旋轉關係。
舉個例子,假如 順時針旋轉 以內能得到 ,那麼稱 在 的右邊,否則在左邊或共線。
正題
這個Graham掃描法其實很好理解,主要有下面幾步:
- 找到一個縱座標最小,在此基礎上橫座標最小的點,可以知道這個點肯定在凸包上,以這個點作爲原點重新建立座標系。
- 將所有點按極角從小到大排序,極角就是這個點到原點這條線段與 軸的夾角,極角相同的將離原點更近的放前面
- 維護一個棧,記錄當前凸包上的點,然後將每個點依次嘗試放入。
重點就是步驟 ,如何進行這個嘗試。
可以發現,按極角排序後,其實相當於將所有點按逆時針排序。凸包有一個性質,即對於凸包上任意兩個相鄰的點 ,所有點都在直線 的左邊,或者就在線段 上,這意味着, 的右邊是不能有點的。
於是,設 爲此時棧頂的兩個點,每次插入一個新點 時,如果 在 的右邊或在直線 上並且不在線段 上,那麼 肯定就不是凸包上的點,將 踢掉,重複這個過程直到只剩一個點或 在 左邊了,然後將點 加入棧中。
畫個圖,就是這樣的( 在 左邊):
模板題傳送門
代碼如下:
#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);
}