计算几何学习笔记
点、向量
这部分没啥好讲的。点可以使用坐标表示,向量可以使用点表示。
这里记录一些我知道的基础知识。
向量长度
对于向量\(A(x,y)\),其长度记为\(|A|=\sqrt{x^2+y^2}\)
向量叉积
对于两个向量 \(A(x_1,y_1),B(x_2,y_2)\) 他们的叉积是\(A\times B=x_1\cdot y_2-x_2\cdot y_1\)。叉积的绝对值还等于以这两个向量为邻边的平行四边形面积,即\(|A|\cdot|B|\cdot\sin\theta\),其中\(\theta\)是他们的夹角。叉积的正负号由右手螺旋定则确定。
向量点积
对于两个向量 \(A(x_1,y_1),B(x_2,y_2)\) 他们的点积是 \(A\cdot B=x_1\cdot y_1+x_2\cdot y_2=|A|\cdot|B|\cdot\cos\theta\),其中\(\theta\)是他们的夹角。
凸包
首先将所有点按照 \(x\) 轴为第一关键字,\(y\) 轴为第二关键字排序,用单调栈维护凸包即可。判断是否是凸的可以使用叉积判断。
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 1e5 + 5;
const double eps = 1e-10;
bool eql(double x, double y) { return abs(x - y) < eps; }
struct Dot {
double x, y;
Dot(double _x = 0, double _y = 0) : x(_x), y(_y) {}
Dot operator-(const Dot &d) const { return Dot(x - d.x, y - d.y); }
double operator^(const Dot &d) const { return x * d.y - d.x * y; }
bool operator<(const Dot &d) const { return x < d.x; }
} dt[N];
double dis(Dot x, Dot y) { return sqrt((x.x - y.x) * (x.x - y.x) + (x.y - y.y) * (x.y - y.y)); }
int n, stk[N], tp;
bool vis[N];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%lf%lf", &dt[i].x, &dt[i].y);
sort(dt + 1, dt + n + 1);
//先计算上凸壳
for (int i = 1; i <= n; ++i) {
while (tp >= 2 && ((dt[stk[tp - 1]] - dt[stk[tp]]) ^ (dt[stk[tp]] - dt[i])) > -eps)
vis[stk[tp--]] = 0;
stk[++tp] = i;
vis[i] = 1;
}
vis[1] = 0;
//在围下凸壳的时候需要用 1号点再弹出一些点
int nb = tp;
for (int i = n; i >= 1; --i) {
if (vis[i])
continue ;
while (tp > nb && ((dt[stk[tp - 1]] - dt[stk[tp]]) ^ (dt[stk[tp]] - dt[i])) > -eps)
--tp;
stk[++tp] = i;
}
double ans = 0;
for (int i = 1; i <= tp; ++i)
ans += dis(dt[stk[i]], dt[stk[i % tp + 1]]);
printf("%.2lf\n", ans);
return 0;
}
旋转卡壳
求出凸包。每一条边找到一个点使得这个点距离这条边最远,这条边的两个端点和那个找到的点更新答案。至于如何找到那个点,我们发现凸包上的点和这个线段的距离是单峰的(峰是最值),所以考虑使用双指针的方式解决问题。
注:一个点和其他点的距离并不单调。因为有可能出现这种情况:
你发现中间这个线非常短,而两边的特别长。
另外还需要特判只有两个点的情况,否则会死循环。
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 5e4 + 5;
struct dot {
int x, y;
dot(int _x = 0, int _y = 0) : x(_x), y(_y) {}
dot operator-(const dot &d) const { return dot(x - d.x, y - d.y); }
int operator*(const dot &d) const { return x * d.y - y * d.x; }
bool operator<(const dot &d) const { return (x == d.x) ? (y < d.y) : (x < d.x); }
} dt[N];
int dis(dot a, dot b) { return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); }
int n, stk[N];
bool use[N];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d%d", &dt[i].x, &dt[i].y);
sort(dt + 1, dt + n + 1);
int tp = 0;
for (int i = 1; i <= n; ++i) {
while (tp >= 2 && (dt[stk[tp - 1]] - dt[stk[tp]]) * (dt[stk[tp]] - dt[i]) >= 0) {
use[stk[tp]] = 0;
--tp;
}
use[i] = 1;
stk[++tp] = i;
}
use[1] = 0;
int rec = tp;
for (int i = n; i >= 1; --i) {
if (use[i]) continue ;
while (tp > rec && (dt[stk[tp - 1]] - dt[stk[tp]]) * (dt[stk[tp]] - dt[i]) >= 0)
--tp; //这里是>=0,为了避免凸包上出现三点共线。
stk[++tp] = i;
}
if (tp == 3) {
printf("%d\n", dis(dt[stk[1]], dt[stk[2]]));
return 0;
}
int ans = 0, pt = 1;
for (int i = 1; i < tp; ++i) {
int x = stk[i], y = stk[i + 1];
while ((dt[y] - dt[stk[pt]]) * (dt[x] - dt[stk[pt]]) <= (dt[y] - dt[stk[pt % tp + 1]]) * (dt[x] - dt[stk[pt % tp + 1]])) //必须是<=。
pt = pt % (tp - 1) + 1;
ans = max(ans, max(dis(dt[x], dt[stk[pt]]), dis(dt[y], dt[stk[pt]])));
}
printf("%d\n", ans);
return 0;
}