计算几何做题(2026summer)

按照做题顺序,对每一道题,给出一些简单的思路与评价,未必给出代码。

如果想要代码(有人会看吗?),可以联系我。

对于较难或者新颖的题目重点介绍。

21/07/2026 -

模板见 https://www.cnblogs.com/dbg-8/p/-/cg

P1995 [NOI2011] 智能车比赛

\(O(n^2)\) 贪心+模拟(说是 DP 也行),由 \(n\) 个矩形的右边边界产生 \(2n\) 个边界点。从每个点开始,递推算出起点到每个可达边界点的最小距离,同时处理这个点到终点(若可达)的距离。但是细节很多,我交了好几发才过,具体看洛谷题解区。

也不知道为什么给紫,下位蓝差不多吧。

P2533 [AHOI2012] 信号塔

裸的随机增量法板子,直接复制就行。

P10671 BZOJ1278 向量 vector

注意到答案一定是连续且在同一侧的(猜的,不会证),极角排序后枚举 \(l, r\) 即可。注意对每个符合的 \(l, r\) 都要处理答案,且 \(r<l\) 时更新 \(r:=l\),否则我的写法就 10 分,也不知道为什么那么少。

P3680 [CERC2016] 凸轮廓线 Convex Contour

若对圆取上下左右四个点,对三角形、正方形直接取节点,求凸包后,对凸包内所有点求直线距离,则仅有以下两种情况会计算错误的:

  1. 圆弧,这时会求成直线的长度
  2. 三角形上顶点与圆上顶点相连时,不是一条直线而是直线+圆弧

记录凸包中每个点的类型,便可找到这两种情况。第一种情况的正确长度固定为 \(\frac{\pi}{4}\),现在考虑第二种情况怎么办。令 \(w\) 为连续的三角形个数,例如 \(w=3\) 时形如 ▲▲▲●,凸包中的点就形如最左边的三角形顶点\(A(0.5-w, \frac{\sqrt{3}-1}{2})\) 与半径为 \(0.5\) 的圆 \(B(0.5,0.5)\) 的上顶点。

要求上切点 \(C\),可以设 \(C(0.5-\cos a, 0.5+ \sin a)\),由于\(w < n \leq 30\), 取值较小,可以直接打表(代码在最后):枚举 \(a\),每次加上一个极小值 \(EPS\), 使得 \(|AC|^2+0.25-|AB|^2 \rightarrow 0\)

得到半径 \(BC\)\(x\) 轴的夹角 \(a\) 之后,正确的长度就是 \(|AB|+(\frac{\pi}{2}-a)*0.5\)

整体思路就是先求凸包,再特判凸包中的特殊情况。这种方法的编程思路会更加清晰。

const double EPS = 1e-8;
const double PI = acos(-1.0);
const double sqrt3h = sqrt(3.0) / 2.0;

struct NODE {
	double x, y;
	int k;
	NODE (void) { x = y = 0.0; k = -1; }
	NODE (double xx, double yy, int kk) { x = xx, y = yy, k = kk; }
	NODE operator + (const NODE &rhs) { return NODE { x + rhs.x, y + rhs.y, -1 }; };
	NODE operator - (const NODE &rhs) { return NODE { x - rhs.x, y - rhs.y, -1 }; };
	double operator ^ (const NODE &rhs) const { return x * rhs.y - y * rhs.x; };
} nd;
inline bool onleft (NODE &x, NODE y, NODE z) {
	if (((y - x) ^ (z - x)) > EPS) return 1;
	return 0;
}
inline double dis2 (NODE x, NODE y) {
	NODE u = x - y; 
	return u.x * u.x + u.y * u.y;
}

inline std:: vector < NODE > gettb (std:: vector < NODE > &a) {
	for (auto &i: a) if (i.x == a[0].x && i.y < a[0].y || i.x < a[0].x) std:: swap(a[0], i);
	std:: sort(a.begin() + 1, a.end(), [&](NODE x, NODE y)  {
		return ((x - a[0]) ^ (y - a[0])) == 0.0 ? (x.x == y.x ? x.y < y.y : x.x < y.x) : onleft(a[0], x, y);
	});
	std:: stack < int > s;
	s.push(0), s.push(1);
	for (int i = 2; i < a.size(); ++i) {
		while (s.size() >= 2) {
			int p = s.top(); s.pop();
			int q = s.top();
			if (onleft(a[q], a[p], a[i])) {
				s.push(p);
				break;
			}
		}
		s.push(i);
	}
	std:: vector < NODE > tb;
	while (s.size()) tb.push_back(a[s.top()]), s.pop();
	std:: reverse(tb.begin(), tb.end());
	return tb;
}

const double CA[31] = {
	0, 1.43291123998231, 1.50334167997969, 1.52600074984198, 1.53724487977365, 
	1.54397185973276, 1.54845013970555, 1.55164633968612, 1.55404229967156, 
	1.55590519966024, 1.55739517965118, 1.55861404964377, 1.55962963963760, 
	1.56048890963238, 1.56122536962790, 1.56186359962403, 1.56242200962063, 
	1.56291471961764, 1.56335265961498, 1.56374449961259, 1.56409713961045, 
	1.56441618960851, 1.56470622960675, 1.56497103960514, 1.56521378960367, 
	1.56543710960231, 1.56564324960106, 1.56583411959990, 1.56601134959882, 
	1.56617635959782, 1.56633036959688
};

inline double getd (NODE u, NODE v) {
	if (u.k > 0 && v.k > 0) {
		if (u.y == v.y) return fabs(u.x - v.x);
		return PI / 4.0;
	}
	if (u.k < 0 && v.k > 0) std:: swap(u, v);
	if (u.k > 0 && v.k < 0) {
		int k = u.k + v.k;
		if (k < 0) k = -k;
		double a = CA[k];
		NODE t, o;
		o.x = .5 - (double)k, o.y = sqrt3h; 
		t.x = .5 - cos(a) * .5, t.y = .5 + sin(a) * .5;
		return (PI * .5 - a) * .5 + sqrt(dis2(t, o));
	}
	return sqrt(dis2(u, v));
}

inline void kagari () {
	int n; std:: string str; std:: cin >> n >> str;
	std:: vector < NODE > a;
	for (int i = 0; i < n; ++i) {
		if (str[i] == 'S')
			a.push_back({ (double)i, 0.0, 0 }),
			a.push_back({ (double)i, 1.0, 0 }),
			a.push_back({ (double)i + 1, 0.0, 0 }),
			a.push_back({ (double)i + 1, 1.0, 0 });
		else if (str[i] == 'T')
			a.push_back({ (double)i, 0.0, 0 }),
			a.push_back({ (double)i + 1, 0.0, 0 }),
			a.push_back({ (double)i + 0.5, sqrt3h, -i - 1 });
		else
			a.push_back({ (double)i + 0.5, 0.0, i + 1 }),
			a.push_back({ (double)i + 0.5, 1.0, i + 1 }),
			a.push_back({ (double)i, 0.5, i + 1 }),
			a.push_back({ (double)i + 1, 0.5, i + 1 });
	}
	
	auto tb = gettb(a);
	double ans = 0;
	for (int i = 0; i < tb.size(); ++i) ans += getd(tb[i], tb[(i + 1) % tb.size()]); 
	printf("%.10f\n", ans);
	return;
}
int main () {
	kagari();
	return 0;
}

事实上打表部分的代码跑的也很快……不打表也可以直接过题,然而我一开始的思路就是打表于是就这样写了。

std:: vector < double > j;
for (int w = 1; w <= 30; ++w) {
	double a = 1.43;
	double res, R;
	NODE o, u, U; 
	o.x = .5 - (double)w, o.y = sqrt3h; 
	U.x = U.y = .5;
	double q = dis2(o, U) - .25; res = q;
	if (w > 1) a = 1.50; 
	while (a < 1.566331) {
		u.x = .5 - cos(a) * .5, u.y = .5 + sin(a) * .5;
		double r = fabs(dis2(u, o) - q);
		if (r < res) res = r, R = a, U = u;
		a += 1e-8;
	}
	// printf("%.14f %.14f    %.14f %.14f %.14f , %f\n", res, q, R, U.x, U.y, dis2(o, U));
	j.push_back(R);
}
for (auto &i: j) printf("%.14f, ", i);

P14802 [CCPC 2024 哈尔滨站] 凹包

题目大意:对于一个多边形,若它非凸,则他就是凹的。给定平面上 \(n \leq 10^5\) 个点,问能否选择其中的一些或全部点,组成一个凹多边形,并求其面积最小值。

对所有点 \(a\) 求凸包 \(b\),并设不在凸包内的点为 \(d=a-b\)。若 \(a\)\(b\) 点数相同则不能组成凹多边形。

凹多边形的面积最小时,其一定是凸多边形加上内部的一个点组成的。这个凸多边形越大越好,直接取 \(b\)。这时就有 \(S_{凹多边形}=S_{凸包}-S_{凹进去部分}\)

于是问题转化为在 \(d\) 中取一个点 \(L\),使其与 \(b\) 中的一条边形成的三角形面积最小。注意到这个点越靠“外”越好,则对 \(d\) 求凸包 \(c\),这时 \(b, c\) 均有序,枚举 \(b\) 的每一条边,根据单调性,便可以以 \(O(n)\) 的均摊时间复杂度找到此时 \(c\) 中使得三角形面积最小的那个点。

注意

求凸包的时候记得特判点的个数是否 \(\leq 2\)

凸包板子没特判,找了半天才发现问题……

// 14:52 -> 
#include <stdio.h>
#include <algorithm>
#include <vector>
#include <stack>
#define ll long long
#define veno std:: vector < NODE >

bool ind[100003];
struct NODE {
	ll x, y;
	NODE operator + (const NODE &rhs) const { return NODE { x + rhs.x, y + rhs.y }; }
	NODE operator - (const NODE &rhs) const { return NODE { x - rhs.x, y - rhs.y }; }
	ll operator ^ (const NODE &rhs) const { return x * rhs.y - y * rhs.x; }
};

inline ll cross (const NODE &x, const NODE &y, const NODE &z) { return ((y - x) ^ (z - x)); }

inline bool onleft (const NODE &x, const NODE &y, const NODE &z) {
	if (cross(x, y, z) > 0) return 1; 
	return 0;
}

auto gettb (veno &a) {
	veno tb;
	if (a.size() <= 2) {
		for (int i = 0; i < a.size(); ++i) ind[i] = 0, tb.push_back(a[i]);
		return tb;
	}
	for (int i = 1; i < a.size(); ++i) 
		if (a[i].x == a[0].x && a[i].y < a[0].y || a[i].x < a[0].x) std:: swap(a[0], a[i]);
	std:: sort(a.begin() + 1, a.end(), [&] (const NODE &x, const NODE &y) {
		return ((x - a[0]) ^ (y - a[0])) == 0 ? (x.x == y.x ? x.y < y.y : x.x < y.x) : onleft(a[0], x, y);
	});
	std:: stack < int > s;
	s.push(0), s.push(1);
	for (int i = 2; i < a.size(); ++i) {
		while (s.size() >= 2) {
			int p = s.top(); s.pop();
			int q = s.top();
			if (onleft(a[q], a[p], a[i])) {
				s.push(p); break;
			}
		}
		s.push(i);
	}
	while (s.size()) tb.push_back(a[s.top()]), ind[s.top()] = 0, s.pop();
	std:: reverse(tb.begin(), tb.end());
	return tb;
}

inline void kagari () {
	int n; scanf("%d", &n);
	veno a(n); for (auto &i: a) scanf("%lld %lld", &i.x, &i.y);
	for (int i = 0; i < n; ++i) ind[i] = 1;
	
	auto b = gettb(a);
	if (b.size() == n) {
		puts("-1");
		return;
	}
	veno d; for (int i = 0; i < n; ++i) if (ind[i]) d.push_back(a[i]);
	auto c = gettb(d);
	
	int l = 0; ll ans = 0, ans2 = 0;
	for (int i = 2; i < b.size(); ++i) ans2 += cross(b[0], b[i - 1], b[i]);
	
	ans = ans2;
	
	for (int i = 0; i < c.size(); ++i) {
		ll s = cross(b[0], b[1], c[i]);
		if (s < ans) l = i, ans = s;
	}
	for (int i = 1; i < b.size(); ++i) {
		int t = 0; 
		while (t <= c.size() && 
			cross(b[i], b[(i + 1) % b.size()], c[l % c.size()]) 
		 >= cross(b[i], b[(i + 1) % b.size()], c[(l + 1) % c.size()]))
			++t, ++l;
		ans = std:: min(ans, cross(b[i], b[(i + 1) % b.size()], c[l % c.size()]));
	}
	
	printf("%lld\n", ans2 - ans);
	return;
}
int main () {
	int t; scanf("%d", &t);
	while (t--) kagari();
	return 0;
}

P10876 [COTS 2022] 点组 Točkice

很简单的题,没有紫。虽然凸包模板就是紫。

题目大意:平面上有 \(N \leq 10^5\) 个点 \((x_i,y_i)\),无三点共线,两个人轮流操作,每次选择两个点连一条线段,要求不与之前的线段在非顶点处相交,无法操作者输,问先手必胜还是后手必胜。

将问题转化为平面上 \(n\) 个不三点共线的点,可以连多少个除顶点外不相交的边。判断奇偶性即可。

考虑三角剖分,设凸包大小为 \(m\),仅凸包的情况下,可以连 \(2*m-3\) 个边,不在凸包上的点,每个点已经位于一个三角形内,与三角形顶点连三条边,也就是共 \(3(n-m)\) 条边。于是求凸包后,判断 \(2*m-3+3*(n-m)\) 的奇偶性即可。

注意特判 \(n=1\) 的情况。

posted @ 2026-08-13 22:42  dbg_8  阅读(6)  评论(0)    收藏  举报