文章目录
一.预备知识
1.向量
向量是一类既有大小又有方向的量,如加速度,速度,位移等等
向量的编程表示:
typedef struct{
double x;
double y;
}Vector;
注:平面中的点也是用一对x,y来表示的,和向量是一样的,所以常常如下操作:
typedef struct{
int x;
int y;
}Point;
typedef Point Vector;
既避免了混淆两种变量,又使程序看起来非常简洁
2.向量的叉积
叉积又称叉乘,符号为×。
两个向量叉乘的结果还是一个向量
其运算规则如下:
①所得向量的模为:
∣
a
×
b
∣
=
∣
a
∣
⋅
∣
b
∣
⋅
s
i
n
θ
|a×b|=|a|\cdot|b|\cdot sinθ
∣a×b∣=∣a∣⋅∣b∣⋅sinθ
其中
θ
θ
θ为向量a与向量b的夹角
②所得向量的方向为:
与a,b所在平面垂直,且满足右手定则(由a转向b角度小于180,大拇指所指方向即为叉乘所得向量的方向)
由此可得,如果向量b在向量a的左边(逆时针方向小于180°),则它们的叉乘结果方向垂直纸面向外,反之,如果向量b在向量a的右边(逆时针方向大于180°),则它们的叉乘结果方向垂直纸面向里
↑也就是叉乘在求凸包中的应用了,即求得向量的顺逆时针关系
规定垂直纸面向外为正方向
叉积的几何意义:
高中学过的三角形面积公式:
1
2
∣
a
∣
⋅
∣
b
∣
⋅
s
i
n
θ
\frac{1}{2} |a|\cdot|b|\cdot sinθ
21∣a∣⋅∣b∣⋅sinθ
(因为
∣
b
∣
⋅
s
i
n
θ
|b|\cdot sinθ
∣b∣⋅sinθ即三角形中边a的高)
那么叉积的值也就是向量a,b构成的平行四边形的面积
平面坐标下下叉乘的计算方法:
向量a:
(
a
x
,
a
y
)
(a_x,a_y)
(ax,ay)
向量b:
(
b
x
,
b
y
)
(b_x,b_y)
(bx,by)
a
×
b
=
a
x
⋅
b
y
−
a
y
⋅
b
x
a×b=a_x\cdot b_y-a_y\cdot b_x
a×b=ax⋅by−ay⋅bx
证明:
①叉乘满足分配律:
a
×
(
b
+
c
)
=
a
×
b
+
a
×
c
a×(b+c)=a×b+a×c
a×(b+c)=a×b+a×c
②令 e 1 e_1 e1表示x轴方向的单位向量, e 2 e_2 e2表示y轴方向的单位向量,则向量 a = a x ⋅ e 1 + a y ⋅ e 2 a=a_x\cdot e_1+a_y\cdot e_2 a=ax⋅e1+ay⋅e2,向量 b = b x ⋅ e 1 + b y ⋅ e 2 b=b_x\cdot e_1+b_y\cdot e_2 b=bx⋅e1+by⋅e2
③
a
×
b
a×b
a×b
=
(
a
x
⋅
e
1
+
a
y
⋅
e
2
)
⋅
(
b
x
⋅
e
1
+
b
y
⋅
e
2
)
=(a_x\cdot e_1+a_y\cdot e_2)\cdot(b_x\cdot e_1+b_y\cdot e_2)
=(ax⋅e1+ay⋅e2)⋅(bx⋅e1+by⋅e2)
=
(
a
x
⋅
b
x
⋅
e
1
⋅
e
1
)
+
(
a
x
⋅
b
y
⋅
e
1
⋅
e
2
)
+
(
a
y
⋅
b
x
⋅
e
2
⋅
e
1
)
+
(
a
y
⋅
b
y
⋅
e
2
⋅
e
2
)
=(a_x\cdot b_x\cdot e_1\cdot e_1)+(a_x\cdot b_y\cdot e_1\cdot e_2)+(a_y\cdot b_x\cdot e_2\cdot e_1)+(a_y\cdot b_y\cdot e_2\cdot e_2)
=(ax⋅bx⋅e1⋅e1)+(ax⋅by⋅e1⋅e2)+(ay⋅bx⋅e2⋅e1)+(ay⋅by⋅e2⋅e2)
由
∣
a
×
b
∣
=
∣
a
∣
⋅
∣
b
∣
⋅
s
i
n
θ
|a×b|=|a|\cdot|b|\cdot sinθ
∣a×b∣=∣a∣⋅∣b∣⋅sinθ可得:
e
1
⋅
e
1
=
0
;
e
2
⋅
e
2
=
0
e_1\cdot e_1=0;e_2\cdot e_2=0
e1⋅e1=0;e2⋅e2=0(夹角为0)
e
1
⋅
e
2
=
1
;
e
2
⋅
e
1
=
−
1
e_1\cdot e_2=1;e_2\cdot e_1=-1
e1⋅e2=1;e2⋅e1=−1
因为y轴在x轴左边,所以前者为正后者为负,又因为单位向量所以值为1
综上,即得 a × b = a x ⋅ b y − a y ⋅ b x a×b=a_x\cdot b_y-a_y\cdot b_x a×b=ax⋅by−ay⋅bx
叉乘计算代码:
double Cross(Vector a,Vector b){
return A.x*B.y-A.y*B.x;
}
tips:叉乘英文为Cross Product
二.Andrew 算法
1.凸包:
将给定点包围在内部的面积最小的凸多边形
因为要满足面积最小,凸包必然是由这给定点中的若干个组成的
2.Andrew 算法
①将所有点按x坐标从小到大排序(如果x相同,则y从小到大排序),可以通过反证法证明:x坐标最小的那个点必然是凸包上的一个点
②不妨令坐标都被存放在数组p(排过序)中,并用一个Ch数组存放凸包(tips:凸包英文Convexhull),取出数组p中的前两个点
p
1
p_1
p1、
p
2
p_2
p2加入凸包
③判断
p
3
p_3
p3与
p
1
p
2
p_1p_2
p1p2(
p
1
p_1
p1、
p
2
p_2
p2构成的向量)的关系:
如果是第1种情况,
p
3
p_3
p3在向量
p
1
p
2
p_1p_2
p1p2的下方(即
p
1
p
3
p_1p_3
p1p3在
p
1
p
2
p_1p_2
p1p2的顺时针小于180°的位置),那么我们就可以判断p2必定不是凸包上的点,于是把
p
2
p_2
p2点从凸包中删除
如果是第2种情况,则 p 2 p_2 p2仍可能是凸包上的点,不做处理
然后将 p 3 p_3 p3加入凸包
④不断重复上述操作,判断新的点是否在“前进方向”的上方,(即最近加入凸包的两个点形成的向量),如果不是,则需要删除最近加入凸包的那个点,注意删除完后还需要继续判断
用栈来表示可以更好理解,不断向栈加入点,遇到新点时和栈顶的两个点进行形成的向量比较位置关系,若在下方则删除栈顶的点,然后继续和栈顶的两个点比较,直到新点处于上方为止(或者栈中点不足两个)。
⑤这样不断操作直到点 p n p_n pn,就求出了凸包的下半部分,然后反过来从 p n p_n pn开始,重复操作就能求出凸包的上半部分。合起来就得到了一个完整的凸包
三.模板
①判断新点与“前进方向”的位置关系通过前文所讲的向量叉积实现,根据结果正负性判断方向
②这道题用了栈的思想,但是用数组模拟也可以
③我写了一个P_to_V函数来描述点与向量的变换,更方便与简洁的写法是重载运算符,但我不会
#include<cstdio>
#include <algorithm>
using namespace std;
typedef struct{
int x;
int y;
}Point;
typedef Point Vector;
Point P[50005];
Point Ch[50005]; //凸包
int Cross(Vector A,Vector B){
return A.x*B.y-A.y*B.x;
}
bool cmp(Point a,Point b){
if(a.x!=b.x)
return a.x<b.x;
return a.y<b.y;
}
int dis(Point a,Point b){ //两点间距离的平方
return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
Vector P_to_V(Point a,Point b){ 两点
Vector ret;
ret.x=b.x-a.x;
ret.y=b.y-a.y;
return ret;
}
int main() {
int N;
scanf("%d",&N);
for(int i=0;i<N;i++)
scanf(" %d %d",&P[i].x,&P[i].y);
sort(P,P+N,cmp);
int cnt=0;
for(int i=0;i<N;i++){
while(cnt>1&&Cross(P_to_V(Ch[cnt-1],Ch[cnt-2]),P_to_V(P[i],Ch[cnt-2]))<0)
cnt--;
Ch[cnt++]=P[i];
}
int vis=cnt;//前vis个点可以保证是凸包的一部分
for(int i=N-2;i>=0;i--){
while(cnt>vis&&Cross(P_to_V(Ch[cnt-1],Ch[cnt-2]),P_to_V(P[i],Ch[cnt-2]))<0)
cnt--;
Ch[cnt++]=P[i];
}
return 0;
}