【学习笔记】凸包

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

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

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站学习。推荐链接

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

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

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

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

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

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

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

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

\(a \times b >0\)\(b\)\(a\)的右边

\(a \times b <0\)\(b\)\(a\)的左边

\(a \times b =0\)则两点共线

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

posted @ 2019-08-09 11:38  fmj_123  阅读(357)  评论(0编辑  收藏  举报