凸包相关

发布时间 2023-05-27 10:14:06作者: Aisaka_Taiga

凸包

二维凸包

凸多边形是指所有内角大小都在 \(\left[ 0,\pi \right]\) 范围内的简单多边形。

凸包就是指在平面内能包含所有给定点的最小凸多边形叫做凸包。

可以以下面的例子来形象理解一下。

下面是一堆木桩,农夫约翰想要围成一个围栏,需要保证所有的木桩都在围栏内,但是约翰想尽量用少的花费。

image

显然我们围着最外面的木桩围起来是最优的

image

所以我们可以这么理解凸包:平面凸包是指覆盖平面上 \(n\) 个点的最小的凸多边形。

那我们怎么让程序围起最外面的木桩呢?

Graham 算法

本质:

Graham 扫描算法维护一个凸壳,通过不断在凸壳中加入新的点和去除影响凸性的点,最后形成凸包。

凸壳就是凸包的一部分。

排序

我们的 Graham 算法的第一步就是对点集进行排序,这样能保证其有序性,从而在后续的处理中更高效。

我们首先先择一个 \(y\) 值最小(如有相同选 \(x\) 最小)的点,记为 \(p_{1}\)

剩下点集中按照极角的大小逆时针排序,然后编号为 \(p_{2}-p_{m}\)

image

我们按照排序结束时的顺序枚举每一个点,依次连线,这里可以使用一个栈来存储,每次入栈,如果即将入栈的元素与栈顶两个元素所构成了一个类似于凹壳的东西,那么显然处于顶点的那个点一定不在这个点集的凸包上,而他正好在栈顶,所以把它弹出栈,新点入栈。

但是,新来的点有可能既踢走了栈顶,再连接新的栈顶元素后却发现仍然可以踢出,此时就不能忘记判断。

总的时间复杂度为 \(O(n\log n)\)

Andrew 算法

  1. 对所有点进行排序,以 \(x\) 为第一关键字,以 \(y\) 为第二关键字。第 \(1,n\) 两个点一定在凸包上。

  2. 先顺序枚举所有点,求下凸包。用栈来维护当前在凸包上的点;新点入栈前,总要判断该弹出哪些旧点。只要新点处在由栈顶两点构成的有向直线的右侧或共线,就弹出旧点。不能再弹出的时候,新点入栈。

  3. 逆序枚举所有点,求上凸包,用栈维护,和上面一样。

总时间复杂度为 \(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;
}