【学习笔记】凸包

想学斜率优化,先把先决条件凸包给肝了。然后完全不懂,于是就咕了。顺便看了一下线代(其实并不会)

讲真这东西耗了我半个暑假

20210305更新:po学了向量回来了发现这写的什么玩意,已更新完毕,您可从更新部分跳至凸包部分。同时,原“前置知识:叉积”部分已弃用并放置于最后。

更新

以下内容大部分在人教A版数学必修2第一章中涉及。
向量是什么,你可以不必知道,只需知道其有两个要素:方向和大小,它可以用有向线段表示。下文中我们将写作(vec{x})(vec{AB})
我们先解释一下向量的坐标:将这条有向线段的起点置于 ((0,0)),则该线段的终点坐标即为该向量的坐标。
再解释一下向量的共线判定,用数乘表示则是:对于向量(vec{a}),(vec{b}),存在一个实数(lambda),使得(lambda vec{a} = vec{b})。而引入坐标后,设(vec{a})坐标为((a_x,a_y))(vec{b})坐标为((b_x,b_y)),则若两向量共线,则有
(huge a_x b_y-a_y b_x=0)
这个形式便是叉积。类似的,通过画图,我们可以得出(vec{a})(vec{b})的关系 对应 叉积与零的关系。(注:我们定义若一个向量(vec{a}),能通过顺时针旋转小于(frac{pi}{2})(vec{b})所在的直线重合,则(vec{b})(vec{a})的右边。反之同理。)
先给出结论,(vec{a})(vec{b})叉积小于零,则(vec{b})(vec{a})的左边
需要注意的是,下文中,我们的原点不一定是((0,0)),而可能是其他点。而对应的,坐标也并非相对于((0,0)),而是我们定义的,会改变的“原点”。一般的,按照下图的情况,我们将会定义原点为(P)

凸包初讲

我们定义,在平面上能包含所有给定点的最小凸多边形叫做凸包。 凸包的周长最小。

简单的说,平面上有一些点,你要用一个凸多边形把它们围起来。并且这个多边形最小。

代码实现

这里介绍一种实现凸包的算法。

首先我们可以将所有点按照其(x)坐标和(y)坐标做双关键字排序。然后我们分别维护上凸壳和下凸壳,维护凸壳我们用单调栈实现。显然第一个点和最末点一定在凸包上,所以我们可以从前往后找可以维护上凸壳,从后往前找可以维护下凸壳。

根据凸多边形的定义,若点(y)在点(x)的右边,则线段(xy)则一定不在凸包上

根据这个原理,我们在单调栈维护点时,若找到这种不凸的点,则将其弹出。直到凸了才将点入栈。要注意的是,判断凸不凸至少需要3个点,即栈中至少要有2个点

以上面图为例,我们首先将(a)(P)入栈,到(b)时,发现(vec{aP})(vec{Pb})的叉积大于0,即(b)(a)点右边,这显然不合法,于是将(P)弹出,此时栈中仅有(a)一个点,将(b)入栈。

对于共线的情况,可以灵活处理。

当我们正着做一遍,倒着做一遍,凸包就求出来了。

例题

本题显然求的是凸包的周长

例题代码

#include<bits/stdc++.h>
using namespace std;
double ans;
int n,q[10100];
struct point
{
	double x,y;
}p[10100];
bool cmp(point x,point y)
{
	 return x.x<y.x||(x.x==y.x&&x.y<y.y);
}
bool dow(point x,point y,point z)
{
	double s=(x.x-z.x)*(y.y-z.y)-(x.y-z.y)*(y.x-z.x);
	if (s<0) return true;
	if (s>=0) return false;
}//叉积
double len(point x,point y)
{
	return sqrt((x.x-y.x) * (x.x-y.x) + (x.y-y.y) *(x.y-y.y));
}//长度计算
int main()
{
	cin>>n;
	for (int i=1;i<=n;i++)
	{
		cin>>p[i].x>>p[i].y;
	}
	sort(p+1,p+1+n,cmp);
    //=====上凸壳====
	int h=0,t=1;
	q[1]=1;
	for (int i=2;i<=n;i++)
	{
		while (t>1&&dow(p[i],p[q[t-1]],p[q[t]]))
		{
			t--;
		}
		q[++t]=i;
	}
    //===下凸壳====
	h=t-1;
	for (int i=n-1;i>0;i--)
	{
		while (h+1<t&&dow(p[i],p[q[t-1]],p[q[t]]))
		{
			t--;
		}
		q[++t]=i;
	}
	for (int i=2 ;i<=t;i++)
	{
		ans+=len(p[q[i]],p[q[i-1]]);
	}
	ans+=len(p[q[t]],p[q[1]]);
	printf("%.2f",ans);
	return 0;
} 

分割线
以下为弃用部分。

前置知识:叉积

这个属于线性代数,有兴趣的请上B站学习。推荐链接

叉积是一种在[向量空间]中向量的[二元运算]——百度百科

下文中将使用(*)代指乘,用( imes)代指叉积

根据某些线代知识,叉积在几何意义上代表的是(a),(b)两个向量为两条边的平行四边形的面积。

而叉积的运算公式(简略版)为

(huge a.x*b.y-a.y*b.x)

注意,这里的(x)(y),都是相对于某一点而言的。例如,下图的(x)(y)相对于点(P)而言,而不是相对于点(O)而言。

求出叉积后,我们可以借助其来判断两个点的相对位置

我们定义若一个向量(x),能通过顺时针旋转小于(180)度角与(y)所在的直线重合,则(y)(x)的右边。反之同理。则叉积与方向的关系为

(a imes b >0)(b)(a)的右边

(a imes b <0)(b)(a)的左边

(a imes b =0)则两点共线

关于叉积,本文仅介绍到这里,若想知道更多关于叉积的信息,请自行搜索。

原文地址:https://www.cnblogs.com/fmj123/p/11326246.html