计算几何(凸包)
凸包的定义
点集Q的凸包是一个最小的凸多边形P,满足Q中每个点都在P的边界或者内部。通俗来讲,就是在一个平面上钉上n个钉子,然后用一根橡皮筋将所有钉子都围住,最后橡皮筋所构成的一个凸多边形。(下面主要讲解用Graham算法求凸包)
Graham算法
- 找到平面上最靠近左下角的点(设为 S 0 S_0 S0)
- 将其他点按极角(以 S 0 S_0 S0为顶点向水平向右方向引一条射线,其他点与 S 0 S_0 S0连线和该水平射线所成角即为该点的极角)排序,极角可用C++自带的atan2直接得到。
- 单调栈思想:将点
S
0
S_0
S0和排序后的第一个点入栈(这里设栈顶第一个点为
S
a
S_a
Sa,第二个为
S
b
S_b
Sb),后面的点
S
i
S_i
Si入栈时进行判断,如果向量
S
a
S_a
Sa
S
i
S_i
Si是否在向量的
S
b
S_b
Sb
S
a
S_a
Sa逆时针方向,如果在就让
S
i
S_i
Si入栈,然后继续判断下一个点,否则
S
a
S_a
Sa出栈,然后循环此过程,直到最后一个点。最后栈中所有点按顺序连起来就构成了这个点集的凸包。
判断向量BC是否在向量AB的逆时针方向,我们要用到叉积,叉积小于0表示在顺时针,大于0则在逆时针(在纸上自己画一画就能看出来了)
洛谷凸包模板题
#include<iostream>
#include<algorithm>
#include<cmath>
#define Vector point
using namespace std;
typedef double db;
const db eps = 1e-6, pi = acos(-1.0);
const int N = 1e5 + 5;
int n, cnt;
int fcmp(db x) //判断一个小数是否大于0,由于精度问题不能直接判断,需要与eps进行比较
{
if(fabs(x) < eps) return 0; // x = 0
else if(x > 0) return 1; // x > 0
else return -1; // x < 0
}
struct point
{
db x, y;
point() {}
point(db x, db y): x(x), y(y){}
Vector operator + (const Vector &A) {return Vector(x + A.x, y + A.y);}
Vector operator - (const Vector &A) {return Vector(x - A.x, y - A.y);}
Vector operator * (const db &A) {return point(x * A, y * A);}
Vector operator / (const db &A) {return point(x / A, y / A);}
bool operator == (const Vector &A) {return !fcmp(x - A.x) && !fcmp(y - A.y);}
point rotate_90s() {return point(y, -x);}
point rotate_90n() {return point(-y, x);}
}a[N], s[N];
db dot(const Vector &A, const Vector &B) // 点积
{
return A.x * B.x + A.y * B.y;
}
db cross(const Vector &A, const Vector &B) // 叉积
{
return A.x * B.y - A.y * B.x;
}
db dist(const point &A, const point &B) // 距离
{
return sqrt((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y));
}
bool cmp(const point A, const point B) //排序函数
{
if(atan2(A.y - a[0].y, A.x - a[0].x) != atan2(B.y - a[0].y, B.x - a[0].x))
return atan2(A.y - a[0].y, A.x - a[0].x) < atan2(B.y - a[0].y, B.x - a[0].x);
return dist(a[0], A) < dist(a[0], B); //如果极角相同则按距离从小到大排序
}
bool check(point &A, point &B) //判断两向量的叉积是否小于0
{
db C = cross(A - a[0], B - a[0]);
if(C > 0) return 1;
else if(fcmp(C) == 0) return 1;
else return 0;
}
int main()
{
cin >> n;
for(int i = 0; i < n; i++)
{
cin >> a[i].x >> a[i].y;
if(i && a[i].y < a[0].y)
{
point t = {a[i].x, a[i].y};
a[i] = a[0]; a[0] = t;
}
else if(a[i].y == a[0].y && a[i].x < a[0].x)
{
point t = {a[i].x, a[i].y};
a[i] = a[0]; a[0] = t;
}
}
sort(a + 1, a + n, cmp);
s[0] = a[0]; //用数组模拟栈,更方便
for(int i = 1; i < n; i++)
{
while(cnt && fcmp(cross(s[cnt] - s[cnt - 1], a[i] - s[cnt - 1])) <= 0) cnt--; //叉积小于等于0就出栈
s[++cnt] = a[i]; //进栈
}
s[++cnt] = a[0];
db res = 0;
for(int i = 0; i < cnt; i++)
res += dist(s[i], s[i + 1]); //求凸包所围成的周长
printf("%.2lf", res);
return 0;
}