preparing

凸包

叉积

众所周知,向量 \(\vec{a}\)\(\vec{b}\) 的数量积(或点积、内积)为 \(\vec{a}\cdot \vec{b} = |\vec{a}||\vec{b}|\cos\theta\)(其中 \(\theta = <\vec{a},\vec{b}>\))。

类似地,向量存在向量积(或叉积、外积),定义为:向量 \(\vec{a}\)\(\vec{b}\) 的叉积是一个向量,其模 \(|\vec{a}\times \vec{b}| = |\vec{a}||\vec{b}|\sin\theta\),方向与 \(\vec{a}\)\(\vec{b}\) 都垂直,且符合右手法则。

叉积的几何意义是,其模长等于 \(\vec{a}\)\(\vec{b}\) 以邻边构成的平行四边形面积。

同时,我们可用叉积的正负来判断点在直线的哪一侧。假设我们利用 \((P,\vec{u})\)(直线上一点加上表示方向的向量)来表示一条直线。那么,对于一点 \(Q\),若 \(\vec{PQ}\times \vec{u}>0\),则 \(Q\) 在直线左侧(或下侧),否则若 \(\vec{PQ}\times \vec{u}<0\) 则在右侧(或上侧),否则在直线上。

极坐标系

在航海中,常常运用例如“北偏东 \(30^\circ\ 200m\) 处”来表示方位。像这样,以一点 \(O\) 为极点,\(Ox\) 为极轴,再选择正方向及单位长度就形成了极坐标系。

极坐标系中,点 \(A\) 的位置由其到极点的距离极径 \(|OA| = \rho\) 以及极角 \(\angle{xOA} = \theta\) 决定,故 \(A\) 的坐标可表示为 \((\rho,\theta)\)

平面直角坐标系和极坐标系的坐标转换可表示为 \(\begin{cases}x = \rho\cos\theta \\ y = \rho\sin\theta \end{cases}\),所以 \(\rho^2 = x^2 + y^2\)\(\theta = \arctan\dfrac{y}{x}(x\neq 0)\)。在 \(\texttt{cmath}\) 中,有 \(\operatorname{atan2}(y,x)\) 函数来计算 \(\theta\)

凸包

凸多边形

凸多边形即所有内角大小均在 \([0,\pi]\) 之间的多边形,所有点都在任意一条边所在的直线同侧,任意两点间连线不在凸多边形外。

凸包

给定集合 \(X\),所有包含 \(X\) 的凸集的交集 \(S\) 称为 \(X\) 的凸包。在平面内,可以看作是用一根橡皮筋包含所有的点时的形态。

凸包是包住所有给定点的图形中周长最小的。若是凹多边形,如下图,根据三角形任意两边之和大于第三边,橙色部分周长不如红色部分。

求解凸包

Jarvis算法

首先从任意一点 \(A\) 开始,任意作一条直线 \(s\),使得所有点在 \(s\) 同侧,将 \(A\) 与所有点连线,找到 \(\angle{BAs}\) 最小的点 \(B\)\(AB\) 就是凸包上的一条边。

然后以 \(B\) 为上一步中的 \(A\),重复这一过程,得到凸包。

但是,这一过程的复杂度显然很高,为 \(\mathcal{O}(nm)\) 级别(\(n\) 为点数,\(m\) 为凸包上点数),所以还要考虑更优的方法。

Graham算法

首先从 \(y\) 值最小的点 \(A\) 开始,找到剩余点中极角最小的一个入栈,用类似单调栈的方法维护栈中点的凸性。

以上图举例子,以 \(A\) 为极点,将所有点排序(图中 \(A\sim G\))。找到极角最小的点 \(B\) 入栈。

再将 \(C\) 入栈:

然后是 \(D\)

\(E\) 入栈时,发现其在栈顶的直线 \(CD\) 右侧,弹出栈顶 \(D\)\(E\) 在栈顶的直线 \(BC\) 左侧,可以入栈:

\(F\) 入栈时,发现其在栈顶的直线 \(CE\) 右侧,弹出栈顶 \(E\)\(F\) 在栈顶的直线 \(BC\) 左侧,可以入栈:

以此类推完成凸包。

复杂度为 \(\mathcal{O}(n\log n)\)

但是求极坐标太烦,需要一个更加便捷的方法:

Andrew算法

两种算法的区别仅在排序方式不同,Andrew算法按照 \(x\)\(y\) 坐标双关键字升序排序。剩下的操作同上。

此时我们发现结束之后并没有完成整个凸包,只形成了一个下凸壳,所以,我们只需要从 \(G\sim A\) 再做一次就能得到一个完整的凸包。

例题

例 1:P2742 [USACO5.1]圈奶牛Fencing the Cows /【模板】二维凸包

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define maxn 100005
using namespace std;
int n,s[maxn],top=0; double ans=0.0;
struct point{double x,y; bool operator<(const point c)const{return x==c.x?y<c.y:x<c.x;}}a[maxn];
double dis(point xx,point yy){return sqrt(1.0*(xx.x-yy.x)*(xx.x-yy.x)+(xx.y-yy.y)*(xx.y-yy.y));}
double cross(point xx,point yy,point zz){return 1.0*(yy.x-xx.x)*(zz.y-yy.y)-(yy.y-xx.y)*(zz.x-yy.x);}
int main(){
    scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%lf%lf",&a[i].x,&a[i].y); sort(a+1,a+1+n);
    if(n<=2){printf("%.2lf",dis(a[1],a[2])); return 0;} s[++top]=1; s[++top]=2;
    for(int i=3;i<=n;i++){while(top>=2&&cross(a[s[top-1]],a[s[top]],a[i])<=0) top--; s[++top]=i;}
    for(int i=1;i<top;i++) ans+=dis(a[s[i]],a[s[i+1]]); top=0; s[++top]=n; s[++top]=n-1;
    for(int i=n-2;i>=1;i--){while(top>=2&&cross(a[s[top-1]],a[s[top]],a[i])<=0) top--; s[++top]=i;}
    for(int i=1;i<top;i++) ans+=dis(a[s[i]],a[s[i+1]]); printf("%.2lf",ans);
    return 0;
}
posted @ 2023-02-01 11:26  qzhwlzy  阅读(39)  评论(0编辑  收藏  举报