凸包指给出N个点,选择尽量少的点连线,将所有的点全部包含在所连成的凸多边形内。
首先,对于两个点P,Q,若原点为O,可使用叉积$\overrightarrow{OP}*\overrightarrow{OQ}$计算出P与Q相对于O点的方向,若叉积小于0则P在Q的逆时针方向。
对于一个点集,首先搜索出纵坐标最小的点P。假设P不在凸包上,没有两个点连线能够在P点下方,因此P点一定在凸包上。然后,将所有点,以P点为中心,进行排序。与P点夹角最小的点在前,若遇到两点与P共线,则距离小的点在前。
然后,先将第一和第二个点放入栈s,从第三个点开始计算,更新到第i个点时栈s内的元素表示0-i元素凸包上的点。随后执行以下操作:
- 对第i个点,若该点与栈顶点组成的向量,在与栈第二个点形成向量的顺时针方向,即二者叉积大于0,则代表栈顶元素不是凸包的点,将栈顶元素弹出,反复执行该操做。
- 随后将第i个点加入栈内。
- 对第i+1个点执行操作。
最后得到的栈内的元素即为凸包的边,按逆时针方向排序。
但是,这种做法会遇到一种问题,就是N个点共线。只需要对排序后的点集地0,1,N-1这3个点是否共线,如果共线则输出第0与N-1个点的距离即可。
最经典例题是HDU1392与洛谷2742
http://acm.hdu.edu.cn/showproblem.php?pid=1392
https://www.luogu.org/problem/P2742
最后附上打了一下午的HDU1392代码,当时对1个点,2个点,以及共线点没有判断卡了很久。
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef long double ld; typedef unsigned long long ull; typedef pair <int ,int> pii; #define rep(i,x,y) for(int i=x;i<y;i++) #define rept(i,x,y) for(int i=x;i<=y;i++) #define per(i,x,y) for(int i=x;i>=y;i--) #define pb push_back #define mp make_pair #define fi first #define se second #define de(x) cout<< #x<<" = "<<x<<endl #define dd(x) cout<< #x<<" = "<<x<<" " #define mes(a,b) memset(a,b,sizeof a) const int inf= 0x3f3f3f3f; const int maxn=105; pii point[maxn];//存放点,下标从0开始 double dis(const pii &s1,const pii &s2) { return sqrt((s1.fi-s2.fi)*(s1.fi-s2.fi)+(s1.se-s2.se)*(s1.se-s2.se)); } pii operator -(const pii &s1,const pii &s2) { return mp(s1.fi-s2.fi,s1.se-s2.se); } int s[maxn];//栈 int chaji(const pii &s1,const pii &s2)//差积 { return s1.fi*s2.se-s1.se*s2.fi; } bool comp(const pii &s1,const pii &s2) { int x=chaji(s1-point[0],s2-point[0]); if( x>0|| (x==0&&fabs(s1.fi-point[0].fi)<fabs(s2.fi-point[0].fi)) ) return 1; else return 0; } int main() { ios::sync_with_stdio(false); cout.tie(0); cin.tie(0); while(true) { int n,cnt=0;//n为点个数,cnt为凸包点个数 cin>>n; if(!n) break; rep(i,0,n) cin>>point[i].fi>>point[i].se; if(n==1) { cout<<"0.00"<<"\n"; return 0; } else if(n==2) { cout<<fixed<<setprecision(2)<<dis(point[0],point[1])<<"\n"; continue; } int p=0; //将下边界的点放到第一位 rep(i,1,n) if( point[i].se<point[p].se||(point[i].se==point[p].se&&point[i].fi<point[p].fi) ) p=i; swap(point[0],point[p]); sort(point+1,point+n,comp); //所有点共线 if(chaji(point[n-1]-point[0],point[1]-point[0])==0) { cout<<fixed<<setprecision(2)<<dis(point[n-1],point[0])<<endl; continue; } //将第0个和第1个点加入栈 s[cnt++]=0; s[cnt++]=1; rep(i,2,n) { while(chaji( point[s[cnt-1]]-point[i],point[s[cnt-2]]-point[i] )>=0 ) cnt--;//将非凸包的点弹出 s[cnt++]=i; } //凸包计算完成,从0到cnt-1存在s中 double ans=0;//ans为多边形长度 rep(i,0,cnt) ans+=dis(point[s[i]] , point[s[(i+1)%cnt]] ); cout<<fixed<<setprecision(2)<<ans<<"\n"; } return 0; }