【模板】二维凸包

问题描述

二维凸包问题,即求平面上一些点的凸包。凸包,是由平面上一些点连线组成的凸多边形,能够包含平面上所有给定点。本文就是求出凸包上的点是哪些。

例题

Graham算法

1.将所有点按照纵坐标从小到大排序,然后从纵坐标最小的点开始(若有相同的则从横坐标最小的点开始)

怎么求极角呢?用atan2(y0,x0)。这个函数的返回值是直线 $$y=\frac{y0}{x0} * x$$ 斜率的反正切,是一个弧度制角度,值域是$ (-\pi,\ph]$

2.以上述点为起点,其余点为终点,按得到的向量的极角从小到大排序,然后逆时针处理,如图

3.接下来准备一个数组(有点类似于单调队列但是只有一端操作),塞进第一个点,然后挨个把剩下的点往里试塞,用于维护一个凸壳。凸壳是凸多边形的一部分。
如果插入一个点后图形仍是凸包,就直接入队,否则弹出队尾元素直到满足条件。比如现在队列里有(G,C,I,E,H,B),并且是一个凸壳,接下来想让 F 点入队,发现FB这条线一加就是凹边形了,不行,所以让点B退出队列。然后发现现在可以了,把 F 点入队。

现在的问题就是如何判断凹角了。由于我们是逆时针处理的,所以维护这个凸壳的时候,一直是在左转、左转、左转...永远是在上一条射线的左边找寻新点的,所以我们可以求一个差角。根据我们高中的知识,很容易发现:
对于对内倒数第二个点指向最后一个点的向量 u ,和最后一个点指向新点的向量 v ,计算二者与 x 轴正方向的夹角,记作∠1,∠2,当 ∠2 - ∠1为一个正值时,相当于是‘向左转’,否则是向右转。

代码实现

int n, begin;
struct point{
	double x, y;
	int id;
}pt[100010];
bool cmpy(point a, point b){
	return (a.y == b.y) ? (a.x < b.x) : (a.y < b.y);
}
bool cmpjiao(point a, point b){
	return atan2(a.y, a.x) < atan2(b.y, b.x);
}
int stk[100010], h, t;
double ans;
double dis(int i, int j){
	return sqrt((pt[i].x - pt[j].x)*(pt[i].x - pt[j].x) + (pt[i].y - pt[j].y)*(pt[i].y - pt[j].y));
}
bool wrong(int a, int b, int c){
	double tan_1 = atan2(pt[b].y - pt[c].y, pt[b].x - pt[c].x),
	tan_2 = atan2(pt[a].y - pt[b].y, pt[a].x - pt[a].x);
	double u_x = pt[b].x - pt[c].x, u_y = pt[b].y - pt[c].y,
	v_x = pt[a].x - pt[b].x, v_y = pt[a].y - pt[b].y;
	double cos_1 = u_x / sqrt(u_x * u_x + u_y * u_y),
	sin_1 = u_y / sqrt(u_x * u_x + u_y * u_y),
	cos_2 = v_x / sqrt(v_x * v_x + v_y * v_y),
	sin_2 = v_y / sqrt(v_x * v_x + v_y * v_y);
	double sin_3 = sin_2 * cos_1 - sin_1 * cos_2;
	return (sin_3 >= 0) ? 0 : 1;
}
int main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i){
		scanf("%lf%lf", &pt[i].x, &pt[i].y);
		pt[i].id = i;
	}
	sort(pt + 1, pt + 1 + n, cmpy);
	for(int i = 2; i <= n; ++i){
		pt[i].x -= pt[1].x;
		pt[i].y -= pt[1].y;
	}
	pt[1].x -= pt[1].x; pt[1].y -= pt[1].y;
	sort(pt + 2, pt + 1 + n, cmpjiao);
	stk[h = t = 1] = 1;
	for(int i = 2; i <= n; ++i){
		while(t > h && wrong(i,stk[t],stk[t - 1]))
			t--;
		stk[++t] = i;
	}
	stk[0] = stk[t];
	for(int i = 1; i <= t; ++i){
		ans += dis(stk[i - 1], stk[i]);
	}
	printf("%.2lf\n", ans);
	return 0;
}

posted @ 2021-06-18 15:04  咕咕坤  阅读(79)  评论(0编辑  收藏  举报