文章目录
前言:
这是第一次接触凸包,希望把自己学到的东西记录下来,如果认为本文还有待修改的地方,欢迎留言!!!
凸包的前置知识(不难,请自行百度):
- 向量叉乘
- 平面上离散点质心的求法
- 凸包:二维平面上能够恰好将所有点包围起来的最小长度的图形
凸包的基本操作一:求凸包的周长
首先问下大家如果给大家一个多边形,你们会怎么求他的周长?(就比如下面这幅图)
它的周长
C
=
∣
A
B
∣
+
∣
B
C
∣
+
∣
C
D
∣
+
∣
D
A
∣
C=|AB|+|BC|+|CD|+|DA|
C=∣AB∣+∣BC∣+∣CD∣+∣DA∣
为了方便我们肯定是按照点的顺时钟方向或逆时针方向来求,而不是一段一段
所以,我们如果为了求凸包的周长,首先要将其输入的点按顺时钟排序或者逆时针顺序(代码中是按逆时针排序)
排序规则:
- 找出给定点中最左下角的那个点
- 以该点为极点,将剩下的点按照极角从小到大排序(逆时针)
struct point {
double x, y;
point friend operator - (point a, point b) {
return {a.x - b.x, a.y - b.y};
}//重新定义-为向量间的减法
}p[105], s[105];
double X(point a, point b) {//向量间的叉乘
return a.x * b.y - a.y * b.x;
}
bool cmp(point & a, point & b) {
double x = X(a - p[1], b - p[1]);
//如果叉乘大于0,说明b-p[1]向量在a-p[1]的逆时钟方向
if(x > 0) return true;//以p[1]为极点,按照极角从小到大排序
if(x == 0 && dis(a, p[1]) < dis(b, p[1])) return true;//角度相同按距离从小到大排序
return false;
}
找出外凸包:
找出外凸包上的点,首先要判断哪些点符合条件,比如说下面这幅图上的点是符合条件的
A
C
AC
AC能够逆时针转到
B
C
BC
BC说明这三个点符合
—————————————————————————————————
下面这幅图上的连边(ABCDE连边)是不符合条件的
因为
C
E
CE
CE是顺时钟转到
D
E
DE
DE(沿着小于等于180°的角,并且起始点移到同一个点)
那么对于不符合的情况,要一直回退,直到退到只剩一个点,或则满足情况,才能加上该点(上图的情况回退到C点即可,CE相连既符合情况)
s[1] = p[1], s[2] = p[2];//s[2]也会被替换掉,但s[1]不会,因为p[1]为图中最左下角的点,其必然会在凸包上
int t = 2;
for(int i = 3; i <= N; i++) {
while(t > 1 && multi(s[t - 1], s[t], p[i]) <= 0) t--;//只保留最外面的那些点
s[++t] = p[i];
}
计算周长:
double dis(point a, point b) {
point c = a - b;
return sqrt(c.x * c.x + c.y * c.y);
}
for(int i = 1; i <= t; i++) sum += dis(s[i], s[i % t + 1]);
完整代码:(HDU-1392)
//求凸包周长
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
using namespace std;
int N;
struct point {
double x, y;
point friend operator - (point a, point b) {
return {a.x - b.x, a.y - b.y};
}
}p[105], s[105];
double dis(point a, point b) {
point c = a - b;
return sqrt(c.x * c.x + c.y * c.y);
}
double X(point a, point b) {
return a.x * b.y - a.y * b.x;
}
bool cmp(point & a, point & b) {
double x = X(a - p[1], b - p[1]);
if(x > 0) return true;//以p[1]为极点,按照极角从小到大排序
if(x == 0 && dis(a, p[1]) < dis(b, p[1])) return true;//角度相同按距离从小到大排序
return false;
}
double multi(const point & p1, const point & p2, const point & p3) {
return X(p2 - p1, p3 - p1);
}
int main() {
while(scanf("%d", &N) && N) {
for(int i = 1; i <= N; i++) scanf("%lf%lf", &p[i].x, &p[i].y);
if(N == 1) printf("0.00\n");
else if(N == 2) printf("%.2lf\n", dis(p[1], p[2]));
else {
int k = 1;
for(int i = 2; i <= N; i++)
if(p[i].y < p[k].y || p[i].y == p[k].y && p[i].x < p[k].x) k = i;
swap(p[1], p[k]);
sort(p + 2, p + N + 1, cmp);
s[1] = p[1], s[2] = p[2];//s[2]也会被替换掉,但s[1]不会,因为p[1]为图中最左下角的点,其必然会在凸包上
int t = 2;
for(int i = 3; i <= N; i++) {
while(t > 1 && multi(s[t - 1], s[t], p[i]) <= 0) t--;//只保留最外面的那些点
s[++t] = p[i];
}
double sum = 0;
for(int i = 1; i <= t; i++) sum += dis(s[i], s[i % t + 1]);
printf("%.2lf\n", sum);
}
}
return 0;
}
ps:接下来虽然是多边形的一些问题,但是和凸包中计算重复的部分有很多,于是放在这里
凸包的基本操作二:求任意多边形的面积
首先直到两向量
A
B
AB
AB,
B
C
BC
BC的叉乘即是他们所构成的平行四边形面积,那么除以2,便是
A
B
C
ABC
ABC三点构成的三角形面积
因为叉乘是有正负的,于是会有如下的定理,按照顺(逆)时钟相邻两点的叉乘全部相加,最后取绝对值÷2,即为多边形面积
接下来让我们观察这幅图
ps1:箭头偷懒没有打出,×为向量之间的叉乘
- ( O A × O B ) / 2 (OA×OB )/ 2 (OA×OB)/2多算出不属于多边形的面积 -SOAB(叉乘有正负)
- ( O B × O C ) / 2 (OB×OC)/2 (OB×OC)/2多算出不属于多边形的面积 +SOBAE
- ( O C × O D ) / 2 (OC×OD)/2 (OC×OD)/2多算出不属于多边形的面积 +SOED
- ( O D × O A ) / 2 (OD×OA)/2 (OD×OA)/2多算出不属于多边形的面积 -SODA
当这些多余的面积,保留正负相加之后,会刚好抵消,因此这些向量叉乘相加起来除二即为多边形面积
完整代码:(HDU-2036)
//求任意多边形面积
//因为本题是顺时针或逆时针给出,所以无需再排序
//如果不是那么就要选定一个最左下角的点,然后按顺时钟排序
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
using namespace std;
int N;
struct point {
double x, y;
}p[105];
int main() {
while(scanf("%d", &N) && N) {
for(int i = 1; i <= N; i++) scanf("%lf%lf", &p[i].x, &p[i].y);
double sum = 0;
for(int i = 1; i <= N; i++) sum += p[i].x * p[i % N + 1].y - p[i].y * p[i % N + 1].x;
sum = fabs(sum / 2.0);
printf("%.1lf\n", sum);
}
return 0;
}
凸包的基本操作三:求任意多边形的重心
精度不高的算法:
精度高的算法:
首先将
n
n
n边形分成
n
−
2
n-2
n−2个三角形,如图的分法
这里就需要运用到刚刚的前置知识2,将原来是质量的地方改为面积即可(ps:求和符号中的
x
x
x为分成三角形的重心,
s
s
s即为分成三角形的面积)
完整代码:(HDU-1115)
#include<bits/stdc++.h>
using namespace std;
struct point {
double x, y;
point friend operator -(point a, point b) {//对-进行重载
return {a.x - b.x, a.y - b.y};
}
double friend operator *(point a,point b) {//对*进行重载 point*point 相当于X乘
return a.x * b.y - a.y * b.x;
}
}p[1000100];
int T, N;
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d", &N);
for(int i = 1; i <= N; i++) scanf("%lf%lf", &p[i].x, &p[i].y);
double sum_x = 0, sum_y = 0, sum_area = 0, area = 0;
for(int i = 2; i < N; i++) {
area = (p[i] - p[1]) * (p[i + 1] - p[1]) / 2;//算面积
sum_area += area;//算总面积
sum_x += (p[i].x + p[i + 1].x + p[1].x) * area;
sum_y += (p[i].y + p[i + 1].y + p[1].y) * area;
}
printf("%.2lf %.2lf\n", sum_x / sum_area / 3, sum_y / sum_area / 3);
//除3是因为累加时没除3
}
}
参考博客: