计算几何学习笔记

点、向量

这部分没啥好讲的。点可以使用坐标表示,向量可以使用点表示。

这里记录一些我知道的基础知识。

向量长度

对于向量\(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;
}
posted @ 2020-07-02 14:32  acniu  阅读(201)  评论(0编辑  收藏  举报