解题思路
这是一道二维凸包的模板问题
首先我们要做点准备工作
存点
struct Point
{
double x,y;
Point(){}
Point(int x,int y): x(x) , y(y){}
bool operator< (Point B){
if(B.x == x) return B.y > y;
return B.x > x;
}
Point operator- (Point B){return Point(x - B.x , y - B.y);}
};
注意一下重载运算符的部分,这里的<是为了排序使用,后续会说明原因
这里的减法运算是为了利用两个点来构造向量
判断
const int eps = 1e-6;
int sgn(double x)
{
if(fabs(x) <= eps) return 0;
return (x>=0 ? 1 : -1);
}
由于涉及到实数运算,并且计算机处理实数运算操作的时候可能会产生误差,所以不能单纯的通过它本身的运算来进行判断,要设置一个误差,具体是多少可以根据题目要求来定
叉乘
double Cross(Point a,Point b){return a.x * b.y - a.y * b.x;}
计算几何问题绕不开的就是这些关于向量的运算,其中叉乘也是非常基础且关键的运算,本题用到的算法就是以这个为基础的
距离
double dis(Point a,Point b){
return sqrt((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y));
}
本题的目标是围栏的长度
介绍一下核心算法
本题的核心算法叫做Andrew算法
主体思想: 把整个凸包分为上半部分和下半部分,遍历两次得到答案
首先我们要所有的点进行排序,按照x从小到大的顺序排列,如果x相同,按照y从小到大的顺序排序
简单一想你就会发现,这样做第一个肯定是凸包左下角的那个点,最后一个肯定是凸包右上角的那个点
那么聪明的你此时肯定能想到从头到尾遍历一遍,从左下角到右上角,这样下凸包就有了,再从后往前遍历一遍,从右上角到左下角,一个上凸包就有了,啊哈,一整个凸包就出来了
当想到这的时候已经兴致冲冲的开始准备写了,我觉得应该不只有这一种方法判断,但是接下来要讲的就是这个Andrew算法怎么构造上下凸包
这就要用到叉乘的性质
比如说Cross(Point a, Point b) //解释一下这里的Point就是一个向量了
b向量如果在a向量左边,那么Cross的结果就大于0
反之则小于0
结合代码可能更好理解
v[k++] = p[0];
for(int i=1;i<n;i++){
while (k > 1 && sgn(Cross(v[k-1] - v[k-2] , p[i] - v[k-1])) <= 0)
k--;
v[k++] = p[i];
}
以图片为例,我们想要找到下凸包的所有点,就要从左下角的点开始
不断加入点,假设此时加入了1和2点,判断3点的时候,向量1->2 和向量2->3的叉乘应该是大于0的,这就表明加上这个3点,前进方向是往上的,一直这样下去就可以到右上角就可以出现一个下凸包
接下来到4点问题就出现了
向量3->4和向量2->3的叉乘就小于0了,前进方向就变得很奇怪了,因为如果出现了这样的情况,说明前面一定有点和这个4点构成的前进方向是正常的,所以我们就要把3去掉了,再去判断向量2->4和向量1->2,还是一样,所以继续去掉2点,最后就留下了1和4点,从下凸包的角度看,2和3就在包围圈里了
简而言之,就是通过前进方向来判断这个点是不是凸包上的点
上凸包一个原理,这里不过多赘述了,看代码即可
最后求出记录下的点构成的多边形的周长就行了
总代码
代码看着很长,是我想写的更有条理一些,实际上已经被分解出来解读完了
#include<bits/stdc++.h>
using namespace std;
const int eps = 1e-6;
const int N = 1e5 + 5;
struct Point
{
double x,y;
Point(){}
Point(int x,int y): x(x) , y(y){}
bool operator< (Point B){
if(B.x == x) return B.y > y;
return B.x > x;
}
Point operator- (Point B){return Point(x - B.x , y - B.y);}
}v[N],p[N];
int sgn(double x)
{
if(fabs(x) <= eps) return 0;
return (x>=0 ? 1 : -1);
}
double Cross(Point a,Point b){return a.x * b.y - a.y * b.x;}
double dis(Point a,Point b){return sqrt((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y));}
int n,k;
double ans;
void Andrew()
{
v[k++] = p[0];
for(int i=1;i<n;i++){
while (k > 1 && sgn(Cross(v[k-1] - v[k-2] , p[i] - v[k-1])) <= 0)
k--;
v[k++] = p[i];
}
int t = k;
for(int i=n-2;i>=0;i--){
while (k > t && sgn(Cross(v[k-1] - v[k-2] , p[i] - v[k-1])) <= 0)
k--;
v[k++] = p[i];
}
if(n > 1) k--;
}
void Distance()
{
for(int i=0;i<k;i++){
ans += dis(v[i],v[(i+1)%k]);
}
}
void solve()
{
Andrew();
Distance();
printf("%.2f",ans);
}
int main()
{
cin >> n;
for(int i=0;i<n;i++) cin >> p[i].x >> p[i].y;
sort(p,p+n);
// for(int i=0;i<n;i++) cout << p[i].x << " " << p[i].y << endl;
solve();
}