题面
有N个点,求凸包长度,0<=N<=10000
分析
前置知识:向量的外积/叉积,用于判断新加入的点能否与原本构成凸壳。
结果的正负性取决于右手定则,垂直纸面向外为正。
凸包的定义是包含所有点的最小凸多边形,可以考虑点都是平面上的钉子,然后用一个有弹性的皮筋从外部围住它们,皮筋会缩紧,使多边形最小,并且最“外”的钉子集合确定了这个图形是凸的。
用的是Andrew算法,求凸壳分为了上下两部分。每新加入一个点,就判断和前两个点的延伸方向是否能构成凸壳,若能则加入;若不能则前两个点后退,直到能将这个新点加入为止(后退而不计算的点被包含在了内部
)
算法首先将所有点从小到大排序,x为第一关键字,y为第二关键字
从左到右求下凸壳
先加入序列中的前两个点,开始递推。
下面是一般性的图解,如果新加入的点是这样:
下面应该加入P13了,经过
的判定,加入这个点可以构成凸包。(等于0是共线,也满足条件可加入)
加入P14:
,不符合凸包条件,凸包后退到P11和P12
但是
仍然成立,继续后退
,加入14
同色的表示进行过判断,现在P12和P13都落入了凸包的内部,凸包路径变成了P9→P10→P11→P14
求
同理运用一遍,从右到左,即可求出上凸壳(代码中我做了reverse处理,将上凸壳翻转成了下凸壳)
代码
#include<stdio.h>
#include<cstdlib>
#include<iostream>
#include<math.h>
#include<vector>
#include<algorithm>
#include<iomanip>
using namespace std;
struct point
{
point() {}
point(double a, double b) :x(a), y(b) {}
double x, y;
int dcmp(double x)//误差取等
{
if (abs(x) < 1e-10)return 0;else return x < 0 ? -1 : 1;
}
bool operator <(const point& other)//为了sort
{
if (dcmp(x - other.x) == 0) {//x1=x2
if (y < other.y)return 1;
else return 0;
}
else
{
if (x < other.x)return 1;
else return 0;
}
}
bool operator == (const point& other)//为了unique
{
return dcmp(x - other.x) == 0 && dcmp(y - other.y) == 0;
}
};
struct Vect//向量
{
double x, y;
Vect(point& p1, point& p2) :x(p2.x - p1.x), y(p2.y - p1.y){}//从点构造向量
double cross(Vect other)//叉积
{
return x * other.y - other.x * y;
}
};
vector<point> v;//存所有点,并进行从小到大排序
int line[10005],ptr=0;//保存凸壳路径
void convex()//求凸壳
{
int size = v.size();
for (int i = 0; i < size; i++)//处理i
{
while (ptr > 1 && Vect(v[line[ptr-1]],v[i]).cross(Vect(v[line[ptr-2]],v[line[ptr-1]]))>0)
//求新加入的点连线 与 原本延伸方向的叉积。根据右手定则,如果是正的,则在原本延伸方向的外侧
ptr--;//后退,直到能与v[i] 形成凸壳
line[ptr++] = i;//符合,入凸包(用栈结构储存)
}
}
double dis(point& a, point& b)
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
int main()
{
ios::sync_with_stdio(false);
int n;
double x, y;
cin >> n;
point temp;
for (int i = 0; i < n; i++)
{
cin >> x >> y;
temp = point(x, y);
v.push_back(temp);
}
sort(v.begin(), v.end());
v.resize(unique(v.begin(), v.end()) - v.begin());//去重点
if (n == 0 || n == 1)cout << "0.00";
else if (n == 2)cout << fixed << setprecision(2) << dis(v[0], v[1]);
double ans = 0;
convex();
for (int i = 1; i < ptr; i++)
{
ans += dis(v[line[i - 1]], v[line[i]]);
}
ptr = 0;
reverse(v.begin(), v.end());//反转,等效于反向求凸壳
convex();
for (int i = 1; i < ptr; i++)
{
ans += dis(v[line[i - 1]], v[line[i]]);
}
cout <<fixed<<setprecision(2)<< ans;
return 0;
}
参考了刘汝佳的《算法竞赛入门经典-训练指南》
——————————2020.1.31————————————
重构了代码,加入了凸包的介绍