简单来说,凸包就是把给定点包围在内部的、面积最小的凸多边形
-
让我们来考虑一下,如何找到一群点的凸包,如下图
-
首先根据这些点的横坐标从小到大排序,如果横坐标相等,那么按照纵坐标从小到大排序,这样这些点就有序了
-
然后我们考虑从左往右扫描,对于第一条边,我们别无选择,只能直接过去,所以就是这样
-
现在我们可能面临一个问题,我们应该去第一个点还是第二个点呢?因为第一个点在第二个点左侧,所以我们现在应该去第一个点,虽然看起来完全不对,我们会在之后进行调整,像现在这样
-
接下来,我们到达 2 2 2这个点了,现在从 1 1 1连接到 2 2 2,显然不对劲,那么我们应该如何判断呢?这里需要用到向量叉积的性质,因为现在所有点的坐标已知,我们根据右手定则可以判断两个向量的方向,那么我们考虑一下如何排除 2 2 2点,看下面几个向量
-
1、2叉积结果是正, 2 2 2、 3 3 3叉积结果是负,这就说明点 2 2 2比点 1 1 1更处于下面的位置,此时我们应该把 1 1 1点删掉,换上 2 2 2,也就是这样
-
这其实有点类似于一个单调栈的思路,每次看当前元素和栈顶元素构成向量的叉积和栈顶元素和栈顶第二个元素的叉积结果,如果是负数,那说明这个点比栈顶点更处于下凸包的位置,那么我们应该弹出栈顶元素换上当前元素;如果叉积结果为 0 0 0,那么这两个向量共线;如果叉积结果为正数,说明还是在当前凸包,这个点可以加进来
-
上述即为凸包的 A n d r e w Andrew Andrew 算法
-
A n d r e w Andrew Andrew算法求凸包程序如下
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <list>
#include <iomanip>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e6 + 100;
const double eps = 1e-6;
int a[MAXN];
int sgn(double x){
if(fabs(x) < eps) return 0;
return x < 0 ? -1 : 1;
}
struct Point{
double x, y;
Point(){
}
Point(double x, double y) : x(x), y(y){
}
bool operator <(const Point &B)const{
return (sgn(x - B.x) < 0 || (sgn(x - B.x) == 0 && sgn(y - B.y) < 0));
}
bool operator == (const Point &B)const{
return x == B.x && y == B.y;
}
Point operator + (const Point &B)const{
return Point(x + B.x, y + B.y);
}
Point operator - (const Point &B)const{
return Point(x - B.x, y - B.y);
}
}s[MAXN], ch[MAXN];
double DIS(Point A, Point B){
return hypot(A.x - B.x, A.y - B.y);
}
double Cross(Point A, Point B){
return A.x * B.y - A.y * B.x;
}
int Convex_hull(int n){
int v = 0;
for(int i=0;i<n;i++){
while(v > 1 && sgn(Cross(ch[v - 1] - ch[v - 2], s[i] - ch[v - 2])) <= 0){
v -= 1;
}
ch[v++] = s[i];
}
int j = v;//上凸包
for(int i=n-2;i>=0;i--){
//为什么从n-2开始?
//n-1是最后一个点,由于排序以后,这个点必然是下凸包的最后一个点
//所以不需要再看
while(v > j && sgn(Cross(ch[v - 1] - ch[v - 2], s[i] - ch[v - 2])) <= 0){
v -= 1;
}
ch[v++] = s[i];
}
if(n > 1) v -= 1;//这里减1的意思是初始节点我们会加进去两次
//也就是说最后初始节点我们又加进去一次
//但是这里如果不减,有时候也不影响答案,这是因为相当于是一个环形结构
return v;
}
- 注意这里面的关于向量叉乘的判断,关键是带等号时候,这是共线的情况,要根据具体要求来判断该不该共线
例题
http://acm.hdu.edu.cn/showproblem.php?pid=1392
- 这个题虽然是板子,但是有些小细节,比如如果最后凸包里面就一个或者两个点怎么办,那显然如果一个点,答案就是 0 0 0,如果两个点,那么答案应该是我们计算出来答案的一半,因为两个点只能连接一条线,没有环
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <list>
#include <iomanip>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e6 + 100;
const double eps = 1e-6;
int a[MAXN];
int sgn(double x){
if(fabs(x) < eps) return 0;
return x < 0 ? -1 : 1;
}
struct Point{
double x, y;
Point(){
}
Point(double x, double y) : x(x), y(y){
}
bool operator <(const Point &B)const{
return (sgn(x - B.x) < 0 || (sgn(x - B.x) == 0 && sgn(y - B.y) < 0));
}
bool operator == (const Point &B)const{
return x == B.x && y == B.y;
}
Point operator + (const Point &B)const{
return Point(x + B.x, y + B.y);
}
Point operator - (const Point &B)const{
return Point(x - B.x, y - B.y);
}
}s[MAXN], ch[MAXN];
double DIS(Point A, Point B){
return hypot(A.x - B.x, A.y - B.y);
}
double Cross(Point A, Point B){
return A.x * B.y - A.y * B.x;
}
int Convex_hull(int n){
int v = 0;
for(int i=0;i<n;i++){
while(v > 1 && sgn(Cross(ch[v - 1] - ch[v - 2], s[i] - ch[v - 2])) <= 0){
v -= 1;
}
ch[v++] = s[i];
}
int j = v;
for(int i=n-2;i>=0;i--){
while(v > j && sgn(Cross(ch[v - 1] - ch[v - 2], s[i] - ch[v - 2])) <= 0){
v -= 1;
}
ch[v++] = s[i];
}
if(n > 1) v -= 1;
return v;
}
int main(){
#ifdef LOCAL
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
while(cin >> n && n){
for(int i=0;i<n;i++){
cin >> s[i].x >> s[i].y;
}sort(s, s + n);
n = unique(s, s + n) - s;
int len = Convex_hull(n);
double ans = 0;
if(len == 0) cout << 0 << '\n';
else{
for(int i=0;i<len;i++){
ans += DIS(ch[i], ch[(i + 1) % len]);
}
if(n == 2) ans /= 2;
cout << fixed << setprecision(2) << ans << '\n';
}
}
return 0;
}
- 此题和上一个题的不同在于这个题是用栅栏围,两个点也需要用两条边来围
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <list>
#include <iomanip>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e6 + 100;
const double eps = 1e-6;
int a[MAXN];
int sgn(double x){
if(fabs(x) < eps) return 0;
return x < 0 ? -1 : 1;
}
struct Point{
double x, y;
Point(){
}
Point(double x, double y): x(x), y(y){
}
bool operator < (const Point &B)const{
return (x < B.x || (x == B.x && y < B.y));
}
bool operator == (const Point &B)const{
return x == B.x && y == B.y;
}
Point operator + (const Point &B)const{
return Point(x + B.x, y - B.y);
}
Point operator - (const Point &B)const{
return Point(x - B.x, y - B.y);
}
}s[MAXN], ch[MAXN];
typedef Point Vector;
double DIS(Point A, Point B){
return hypot(A.x - B.x, A.y - B.y);
}
double Cross(Vector A, Vector B){
return A.x * B.y - A.y * B.x;
}
int Convex_hull(int n){
int v = 0;
for(int i=0;i<n;i++){
while(v > 1 && sgn(Cross(ch[v - 1] - ch[v - 2], s[i] - ch[v - 2])) <= 0){
v -= 1;
}
ch[v++] = s[i];
}
int j = v;
for(int i=n-2;i>=0;i--){
while(v > j && sgn(Cross(ch[v - 1] - ch[v - 2], s[i] - ch[v - 2])) <= 0){
v -= 1;
}
ch[v++] = s[i];
}
if(n > 1) v -= 1;
return v;
}
int main(){
#ifdef LOCAL
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
for(int i=0;i<n;i++){
cin >> s[i].x >> s[i].y;
}sort(s, s + n);
n = unique(s, s + n) - s;
int len = Convex_hull(n);
if(len <= 1) cout << 0 << '\n';
else{
double ans = 0;
for(int i=0;i<len;i++){
ans += DIS(ch[i], ch[(i + 1) % len]);
}
cout << fixed << setprecision(2) << ans << '\n';
}
return 0;
}