凸多边形是指所有内角大小都在 \(\left[ 0,\pi \right]\) 范围内的简单多边形。
凸包就是指在平面内能包含所有给定点的最小凸多边形叫做凸包。
可以以下面的例子来形象理解一下。
下面是一堆木桩,农夫约翰想要围成一个围栏,需要保证所有的木桩都在围栏内,但是约翰想尽量用少的花费。
显然我们围着最外面的木桩围起来是最优的
所以我们可以这么理解凸包:平面凸包是指覆盖平面上 \(n\) 个点的最小的凸多边形。
那我们怎么让程序围起最外面的木桩呢?
本质:
Graham 扫描算法维护一个凸壳,通过不断在凸壳中加入新的点和去除影响凸性的点,最后形成凸包。
凸壳就是凸包的一部分。
我们的 Graham 算法的第一步就是对点集进行排序,这样能保证其有序性,从而在后续的处理中更高效。
我们首先先择一个 \(y\) 值最小(如有相同选 \(x\) 最小)的点,记为 \(p_{1}\)。
剩下点集中按照极角的大小逆时针排序,然后编号为 \(p_{2}-p_{m}\)
我们按照排序结束时的顺序枚举每一个点,依次连线,这里可以使用一个栈来存储,每次入栈,如果即将入栈的元素与栈顶两个元素所构成了一个类似于凹壳的东西,那么显然处于顶点的那个点一定不在这个点集的凸包上,而他正好在栈顶,所以把它弹出栈,新点入栈。
但是,新来的点有可能既踢走了栈顶,再连接新的栈顶元素后却发现仍然可以踢出,此时就不能忘记判断。
总的时间复杂度为 \(O(n\log n)\)
对所有点进行排序,以 \(x\) 为第一关键字,以 \(y\) 为第二关键字。第 \(1,n\) 两个点一定在凸包上。
先顺序枚举所有点,求下凸包。用栈来维护当前在凸包上的点;新点入栈前,总要判断该弹出哪些旧点。只要新点处在由栈顶两点构成的有向直线的右侧或共线,就弹出旧点。不能再弹出的时候,新点入栈。
逆序枚举所有点,求上凸包,用栈维护,和上面一样。
总时间复杂度为 \(O(n\log n)\)。
其实共线的点是不用删的,但是后面计算距离的时候枚举会变多,虽然差不了多少
code:
#include<bits/stdc++.h> #define int long long #define DB double #define N 1000100 using namespace std; int n,top; struct sb{DB x,y;}e[N],stk[N]; inline DB cross(sb a,sb b,sb c){return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);} inline DB js(sb a,sb b){return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));} inline int cmp(sb a,sb b){if(a.x==b.x)return a.y<b.y;return a.x<b.x;} inline DB Andrew() { sort(e+1,e+n+1,cmp); for(int i=1;i<=n;i++) { while(top>1&&cross(stk[top-1],stk[top],e[i])<=0)top--; stk[++top]=e[i]; } int t=top; for(int i=n-1;i>=1;i--) { while(top>t&&cross(stk[top-1],stk[top],e[i])<=0)top--; stk[++top]=e[i]; } DB res=0; for(int i=1;i<top;i++)res+=js(stk[i],stk[i+1]); return res; } signed main() { cin>>n; for(int i=1;i<=n;i++)cin>>e[i].x>>e[i].y; DB ans=Andrew(); printf("%.2lf\n",ans); return 0; }
Convex Hull
这道题目也是一道模板,我们看到里面给了你哪些点是凸包的点,其实可以不用给,因为我们后面能求出来。
我们在算法上选择 Andrew 算法,因为最后要求点从 \(x\) 最小的点开始逆时针输出,这不刚好是我们算法的入栈顺序吗。
这里就体现了手写栈的好处。
#include<bits/stdc++.h> #define int long long #define DB double #define N 1000100 using namespace std; int t,n,top;char c; struct sb{DB x,y;}e[N],stk[N]; inline DB cross(sb a,sb b,sb c){return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);} inline DB js(sb a,sb b){return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));} inline int cmp(sb a,sb b){if(a.x==b.x)return a.y<b.y;return a.x<b.x;} inline void Andrew() { sort(e+1,e+n+1,cmp); for(int i=1;i<=n;i++) { while(top>1&&cross(stk[top-1],stk[top],e[i])<0)top--; stk[++top]=e[i]; } int t=top; for(int i=n-1;i>=1;i--) { while(top>t&&cross(stk[top-1],stk[top],e[i])<0)top--; stk[++top]=e[i]; } } signed main() { cin>>t; while(t--) { top=0; cin>>n; for(int i=1;i<=n;i++)cin>>e[i].x>>e[i].y>>c; Andrew(); cout<<top-1<<endl; for(int i=1;i<top;i++)cout<<(int)stk[i].x<<" "<<(int)stk[i].y<<endl; } return 0; }
参考自:https://www.luogu.com.cn/blog/ShineEternal/convex-hull