【笔记】二维凸包
AgrumeStly · · 算法·理论
Part -999 感谢列表
(排名不分先后)
- 计算几何「OI-Wiki」
- 数论小白都能看懂的平面凸包详解 「ShineEternal的博客」
- 几何画图「GeoGebra」 离线版
- 感谢@rui_er 指出了一个错误
- 感谢@MicroMaker hack,指出了错误
Part 1 前言
限于笔者能力有限,本学习笔记如有错误,并非有意,非常欢迎指出。
Part 2 何为计算几何
学二维凸包,我们首先需要了解的就是计算几何。
计算几何,就是利用计算机建立数学模型解决几何问题。
要用电脑解几何题?数学好的同学们笑了。
我们并不是用计算机算数学卷子上的几何题去了,而是解决一些更加复杂的几何相关问题。
为了解决复杂且抽象的问题,我们一定要选择合适的研究方法。对于计算机来说,给它看几何图形……
Part 3 二维凸包
Part 3.1 凸多边形
凸多边形是指所有内角大小都在
Part 3.2 凸包
「
在平面上能包含所有给定点的最小凸多边形叫做凸包。
其定义为:对于给定集合
Part 3.4 实战演练
Part 3.4.1 P2742 [USACO5.1]圈奶牛Fencing the Cows /【模板】二维凸包
先拿模板题练练手。
题目简述:求一个二维凸包的周长。
拿 Graham 算法做即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#define line cout << endl
using namespace std;
const int NR = 1e5 + 5;
int n;
double ans;
struct point {
double x, y;
};
point p[NR], ps[NR];
double dis (point pa, point pb) { //求两点间距离
return sqrt ((pb.x - pa.x) * (pb.x - pa.x) + (pb.y - pa.y) * (pb.y - pa.y));
}
double cp (point pa1, point pa2, point pb1, point pb2) { //求叉积
return (pa2.x - pa1.x) * (pb2.y - pb1.y) - (pb2.x - pb1.x) * (pa2.y - pa1.y);
}
bool cmp (point px, point py) { //排序
double num = cp (p[1], px, p[1], py);
if (num > 0) return true;
if (num == 0 && dis (p[0], px) < dis (p[0], py)) return true;
return false;
}
int main () {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> p[i].x >> p[i].y;
if(i != 1 && p[i].y < p[1].y) { //去重
swap (p[i].y, p[1].y);
swap (p[i].x, p[1].x);
}
}
sort (p + 2, p + n + 1, cmp);
ps[1] = p[1]; //最低点是肯定在凸包里的
int h = 1;
for (int i = 2; i <= n; i++) {
while (h > 1 && cp (ps[h - 1], ps[h], ps[h], p[i]) <= 0) { //判断是向左还是向右,如果向右就出栈
h--;
}
h++;
ps[h] = p[i];
}
ps[h + 1] = p[1]; //最后一个点跟第一个点相连
for (int i = 1; i <= h; i++) {
ans += dis (ps[i], ps[i + 1]); //累加
}
printf ("%.2lf\n", ans);
return 0;
}
发现本代码被 hack 了?为什么?
我们分析,按照这种算法,那么在所有点都在一条线的时候就会出问题,无法回到起点,那么我们只需特判一下即可。
Part 3.4.2 UVA11626 Convex Hull
这题好像拿 Graham 会 TLE?拿 Andrew罢,也是道模板题。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#define line cout << endl
using namespace std;
const int NR = 1e5 + 5;
const double eps = 1e-7;
int n;
struct point {
double x, y;
point () {}
point (double a, double b) : x (a), y (b) {}
bool operator < (const point &b) const {
if (x < b.x) return 1;
if (x > b.x) return 0;
return y < b.y;
}
point operator - (const point &b) {
return point (x - b.x, y - b.y);
}
};
point p[NR], sp[NR];
int cmp (double x) {
if (fabs (x) < eps) return 0;
return x > 0 ? 1 : -1;
}
double dis (point a, point b) {
return sqrt ((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
double cp (point a, point b) {
return a.x * b.y - a.y * b.x;
}
int Andrew () {
sort (p + 1, p + 1 + n);
int len = 0;
for (int i = 1; i <= n; i++) {
while (len > 1 && cmp (cp (sp[len] - sp[len - 1], p[i] - sp[len - 1])) < 0)
len--;
sp[++len] = p[i];
}
int k = len;
for (int i = n - 1; i >= 1; i--) {
while (len > k && cmp (cp (sp[len] - sp[len - 1], p[i] - sp[len - 1])) < 0)
len--;
sp[++len] = p[i];
}
return len;
}
int main () {
int t;
cin >> t;
while (t--) {
cin >> n;
char c;
for (int i = 1; i <= n; i++)
cin >> p[i].x >> p[i].y >> c;
int t = Andrew();
cout << t - 1 << endl;
for (int i = 1; i < t; i++)
printf ("%.0lf %.0lf\n", sp[i].x, sp[i].y);
}
return 0;
}
The End...