ICPC 中的计算几何题目(2026spring)

Shinnchi "dbg_8" Rann, started from 28/04/2025

一些参考链接:

基本算法与模板 -> 计算几何学习

近几年ICPC区域赛的中的计算几何: https://blog.csdn.net/doyowhat/article/details/134622007

XCPC计算几何题: https://blog.csdn.net/weixin_73725403/article/details/146959510

总结

过几天再写...

算法与模板,见 计算几何学习

以下按照大致的难度排序,困难题可能没有,因为我没有那个水平。中等题就是模板基础上再难一些,简单题就是没有学过基础模板也能做出来的题。

此外,必要的中等与高等数学基础也是必要的。

中等题

ICPC Shenyang R 2025 G. Collision Damage

28042026

\(A\) 的面积为 \(S_A\)\(B\) 的面积为 \(S_B\)。令所有在 \(B\) 某个点 \(Q\)\(A\) 某个点 \(P\) 重合时,\(B\) 上的固定点 \(M\) 此时的坐标 \(M'\) 组成集合 \(D\),对 \(D\) 求凸包,得面积 \(S_D\)。要么看样例,要么积分推导,易得答案即为 \(\dfrac {S_A S_B}{S_D}\)

这是 \(O(nm\log nm)\) 的做法。与凸包模板差不多,赛时好像大多数都是一发过。

注意一些细节:

  • IPS 开小一些, 1e-8 会导致 WA(被卡),1e-10 就可以通过
  • 开 long long
  • atan2() 的时间复杂度可视为常数 10~20,在排序函数中直接使用会导致超时。

\(O(n+m)\) 的做法我也想出来了,差别不大,就是缩小 \(D\) 的大小,直接算出凸包,应该比较好想。考场上在这个数据范围下没必要写了(除非你学过闵可夫斯基和,可以快速打出模板)。过几天,如果有时间,补一下这个写法。

闵可夫斯基和的模板:P4557 [JSOI2018] 战争

#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <vector>
#define ll long long
const double EPS = 1e-10;

int n, m;

struct NODE {
	int x, y; double z;
	inline NODE operator + (const NODE rhs) const {
		return { x + rhs.x, y + rhs.y };
	}
	inline NODE operator - (const NODE rhs) const {
		return { x - rhs.x, y - rhs.y };
	}
	inline int operator * (const NODE rhs) const {
		return x * rhs.y - y * rhs.x;
	}
	inline int operator == (const NODE rhs) const {
		return x == rhs.x && y == rhs.y;
	}
};

inline double getsq (std:: vector < NODE > &a) {
	ll res = 0;
	for (int i = 2; i < a.size(); ++i) res += (a[i - 1] - a[0]) * (a[i] - a[0]);
	return (double)res / 2.0;
}
std:: vector < NODE > a, b, c, d;

inline double atan2 (const NODE &x) {
	return atan2(x.y, x.x);
}

inline double dis (const NODE x, const NODE y) {
	return sqrt((x.x - y.x) * (x.x - y.x) + (x.y - y.y) * (x.y - y.y));
}

inline void kagari () {
	scanf("%d %d", &n, &m);
	a.clear(), b.clear(), c.clear(), d.clear();
	a.resize(n), b.resize(m);
	for (int i = 0; i < n; ++i) scanf("%d %d", &a[i].x, &a[i].y);
	for (int i = 0; i < m; ++i) scanf("%d %d", &b[i].x, &b[i].y);
	
	for (int i = 0; i < n; ++i)
		for (int j = 0; j < m; ++j)
			c.push_back(b[j] + b[0] - a[i]);
	std:: sort(c.begin(), c.end(), [](const NODE &x, const NODE &y) {
		return x.x == y.x ? x.y < y.y : x.x < y.x;
	});
	c.resize(std:: unique(c.begin(), c.end()) - c.begin()); 
	for (int i = 1; i < c.size(); ++i) c[i].z = atan2(c[i] - c[0]);
	std:: sort(c.begin() + 1, c.end(), [](const NODE &x, const NODE &y) {
		return fabs(x.z - y.z) < EPS ? dis(x, c[0]) < dis(y, c[0]) : x.z < y.z;
	});
//	for (auto &i: c) printf("~ %d %d\n", i.x, i.y); 
	d.push_back(c[0]);
	for (int i = 1; i < c.size(); ++i) {
		while (d.size() >= 2 && (d[d.size() - 2] - d[d.size() - 1]) * (d[d.size() - 1] - c[i]) < EPS) d.pop_back();
		d.push_back(c[i]);
	}
//	for (auto &i: d) printf("= %d %d\n", i.x, i.y); 
	
	double sa = getsq(a);
	double sb = getsq(b);
	double sd = getsq(d);
	double ans = sa * sb / sd;
	printf("%.12f\n", ans);
	return;
}
int main () {
	int t; scanf("%d", &t);
	while (t--) kagari();
	return 0;
}

ICPC 2020 Shenyang R A. The Grand Tournament

01052026

官方题解: https://codeforces.com/gym/103202/attachments/download/14072/ICPC_2020_Shenyang_Regional_Tutorial.pdf

注意到当且仅当两条线有端点重合,或这两条线段重合时有答案,其余时候答案一定为 0。然后按照官方题解里那四张图来分类讨论即可。理清楚思路就只需要套一个半平面交的模板,不过代码细节也不少。

一开始搞错了一个情况,写了半天才好。。。

#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <queue>
const double EPS = 1e-10;

struct NODE {
	double x, y;
	inline void read () { scanf("%lf %lf", &x, &y); }
	inline NODE operator + (const NODE rhs) const { return { x + rhs.x, y + rhs.y }; }
	inline NODE operator - (const NODE rhs) const { return { x - rhs.x, y - rhs.y }; }
	inline bool operator == (const NODE rhs) const { return fabs(x - rhs.x) <= EPS && fabs(y - rhs.y) <= EPS; }
	inline double operator ^ (const NODE rhs) const { return x * rhs.y - y * rhs.x; }
} L, R, A, B, C, D; 
struct LINE {
	NODE x, y;
	double z;
	LINE (NODE xx, NODE yy) {
		x = xx, y = yy;
		z = atan2(y.y, y.x);
	}
};
inline NODE inter (LINE a, LINE b) {
	double t = (b.y ^ (a.x - b.x)) / (a.y ^ b.y);
	return { a.x.x + a.y.x * t, a.x.y + a.y.y * t };
}
inline bool onleft (LINE x, NODE y) {
	return (x.y ^ (y - x.x)) >= EPS;
}
inline double dis (const NODE x) {
	return sqrt(x.x * x.x + x.y * x.y);
}
inline double dis (const LINE x) {
	return dis(x.y);
}
inline double atan2 (const NODE x) {
	return atan2(x.y, x.x);
}

inline double bpmj (std:: vector < LINE > &v) {
	std:: sort(v.begin(), v.end(), [](const LINE &x, const LINE &y) { 
		return fabs(x.z - y.z) <= EPS ? (x.y ^ (y.x - x.x)) < 0.0 : x.z < y.z;
	} );
	int m = 1;
	for (int i = 1; i < v.size(); ++i) if (fabs(v[i].z - v[i - 1].z) > EPS) v[m++] = v[i];
	
	std:: deque < NODE > p;
	std:: deque < LINE > q;
	for (int i = 0; i < m; ++i) {
		while (p.size() && !onleft(v[i], p.back())) p.pop_back(), q.pop_back();
		while (p.size() && !onleft(v[i], p.front())) p.pop_front(), q.pop_front();
		if (q.size()) p.push_back(inter(v[i], q.back()));
		q.push_back(v[i]);
	}
	while (p.size() && !onleft(q.front(), p.back())) p.pop_back(), q.pop_back();
	p.push_back(inter(q.front(), q.back()));
	if (p.size() <= 2) return 0.0;
	double res = 0.0;
	for (int i = 1; i < p.size() - 1; ++i) res += ((p[i] - p[0]) ^ (p[i + 1] - p[0]));
	res *= 0.5;
	return res;
}

inline void buildv (std:: vector < LINE > &v) {
	if (v.size()) v.clear();
	v.push_back({L, NODE { R.x, L.y } - L});
	v.push_back({{ R.x, L.y }, R - NODE { R.x, L.y }});
	v.push_back({R, NODE { L.x, R.y } - R});
	v.push_back({{ L.x, R.y }, L - NODE { L.x, R.y }});
}

inline void kagari () {
	L.read(), R.read();
	A.read(), B.read();
	C.read(), D.read();
	
	if (A == D) std:: swap(C, D);
	else if (B == C) std:: swap(A, B);
	else if (B == D) std:: swap(A, B), std:: swap(C, D);
	LINE p = { A, B - A }, q = { C, D - C };
	
	if (A == C && B == D) printf("%.12f\n", (R.y - L.y) * (R.x - L.x));
	else if (A == C) {
		double ans = 0.0;
		std:: vector < LINE > v; buildv(v);
		if (fabs(p.z - q.z) <= EPS) {
			if (dis(p) < dis(q)) std:: swap(B, D);
			v.push_back({ D, { -q.y.y, q.y.x } });
		}
		else {
			v.push_back({ A, { -p.y.y, p.y.x } });
			v.push_back({ A, { -q.y.y, q.y.x } });
		}
		printf("%.12f\n", bpmj(v));
	}
	else {
		if (p.z < 0 || fabs(p.z - acos(-1.0)) <= EPS) std:: swap(A, B), p = { A, B - A };
		if (q.z < 0 || fabs(q.z - acos(-1.0)) <= EPS) std:: swap(C, D), q = { C, D - C };
//		printf("~ %.12f %.12f %.12f\n", p.z, q.z, atan2(C - B));
		if (fabs(p.z - q.z) <= EPS && (fabs(atan2(C - B) - p.z) <= EPS || fabs(p.z - atan2(C - B) - acos(-1.0)) <= EPS || fabs(p.z - atan2(C - B) + acos(-1.0)) <= EPS)) {
			std:: vector < LINE > v; buildv(v);
			v.push_back({ A, { p.y.y, -p.y.x } });
			v.push_back({ B, { -p.y.y, p.y.x } });
			v.push_back({ C, { p.y.y, -p.y.x } });
			v.push_back({ D, { -p.y.y, p.y.x } });
//			for (auto &i: v) printf("~~ %f %f %f %f\n", i.x.x, i.y.y, i.y.x, i.y.y);
			printf("%.12f\n", bpmj(v));
		}
		else puts("0.000000000");
	}
	return;
}

int main () {
	int t; scanf("%d", &t);
	while (t--) kagari();
	return 0;
}

CCPC Henan 2025 L. Astral Decay

from 2025 National Invitational of CCPC (Zhengzhou), 2025 CCPC Henan Provincial Collegiate Programming Contest
02052026

题意:平面中 \(n\leq6666\) 个点中,取三个点 \(A, B, C\)(可以重复选取)使 \(\overrightarrow{AB} \cdot \overrightarrow{AC} = (x_B-x_A)(x_C-x_A)+(y_B-y_A)(y_C-y_A)\) 最小。求这个最小值。

暴力方法的时间复杂度直接 \(O(n^3)\)。而我们只需要把他变成 \(O(n^2)\) 即可。
注意到 \(A=B=C\) 时答案为 \(0\),所以最终答案一定是非正的。

画图观察,当 \(A\) 确定时,要使答案最小,\(B, C\) 一定是在凸包上的(首先,\(A, B\) 确定时 \(C\) 一定在凸包上,而若 \(B\) 不在凸包上,一定有凸包上一点 \(D\) 使得 \(\overrightarrow{AD} \cdot \overrightarrow{AC} \leq \overrightarrow{AB} \cdot \overrightarrow{AC}\))。在这里不给出证明了。
这时有两个情况:

  1. \(A\) 不在凸包上:
    先随便在凸包上取一点作为 \(B\),在凸包上从 \(B\) 开始逆时针找到使 \(\overrightarrow{AB} \cdot \overrightarrow{AC} \leq 0\) 且最小的 \(C\)。然后逆时针枚举 \(B\)\(C\) 也只需要逆时针依次变化。这样双指针的方式,整体时间复杂度为 \(O(n^2)\)
  2. \(A\) 在凸包上:
    与第一种情况差不多。不过此时不能随便取一点了,而是选择凸包上 \(A\) 逆时针的下一个点作为第一个 \(B\)

详细做法可以看代码,应当是较为易懂的。

注意:

  • IPS 要取很小,实测 \(10^{-10}\) 会 WA#61, \(10^{-12}\) 会 WA#63。\(10^{-15}\) 可以 AC。为此我检查了 50min。
  • 然后没了。本题最大数为 \(8×10^18\),无需 __int128。

感觉这题能上黑,\(B, C\) 都在凸包上并不好想吧!IPS 要取那么小,出题人此举完全没有必要!赛时通过 4 人次。

#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <vector>
#define ll long long
const double IPS = 1e-15;

int n, m;

struct NODE {
	ll x, y; double z;
	inline NODE operator + (const NODE rhs) const {
		return { x + rhs.x, y + rhs.y };
	}
	inline NODE operator - (const NODE rhs) const {
		return { x - rhs.x, y - rhs.y };
	}
	inline ll operator * (const NODE rhs) const {
		return x * rhs.x + y * rhs.y;
	}
	inline ll operator ^ (const NODE rhs) const {
		return x * rhs.y - y * rhs.x;
	}
	inline int operator == (const NODE rhs) const {
		return x == rhs.x && y == rhs.y;
	}
};

std:: vector < NODE > a, c, d;

inline double atan2 (const NODE &x) {
	return atan2(x.y, x.x);
}

inline ll dis (const NODE x, const NODE y) {
	return (x.x - y.x) * (x.x - y.x) + (x.y - y.y) * (x.y - y.y);
}

inline int tubao () {
	std:: sort(c.begin(), c.end(), [](const NODE &x, const NODE &y) {
		return x.x == y.x ? x.y < y.y : x.x < y.x;
	});
	c.resize(std:: unique(c.begin(), c.end()) - c.begin()); 
	for (int i = 1; i < c.size(); ++i) c[i].z = atan2(c[i] - c[0]);
	std:: sort(c.begin() + 1, c.end(), [](const NODE &x, const NODE &y) {
		return fabs(x.z - y.z) < IPS ? dis(x, c[0]) < dis(y, c[0]) : x.z < y.z;
	});
	d.push_back(c[0]);
	for (int i = 1; i < c.size(); ++i) {
		while (d.size() >= 2 && ((d[d.size() - 2] - d[d.size() - 1]) ^ (d[d.size() - 1] - c[i])) < 0) d.pop_back();
		d.push_back(c[i]);
	}
	return d.size();
}

#define get(A, B, C) ((B - A) * (C - A))

inline void kagari () {
	ll ans = 0;
	scanf("%d", &n);
	a.resize(n), c.resize(n);
	for (int i = 0; i < n; ++i) scanf("%lld %lld", &a[i].x, &a[i].y), c[i] = a[i];
	m = tubao();
	for (int i = 0; i < n; ++i) {
		#define A a[i]
		#define B d[l % m]
		#define C d[r % m]
		#define CC d[(r + 1) % m]
		int l = 0, r = 0, en = m;
		for (int j = 0; j < m; ++j) if (A == d[j]) l = r = j + 1, en = j + m;
		while (l < en) {
			while (r < en) {
				if (get(A, B, C) >= 0 || get(A, B, C) >= get(A, B, CC))
					r++;
				else
					break;
				if (r == l) break;
			}
			if (r == l) break;
	//		printf("~ %d %d %d  %lld\n", i, l, r, get(A, B, C));
			ans = std:: min(ans, get(A, B, C));
			l++;
		}
	}
	printf("%lld\n", ans);
	return;
}
int main () {
	kagari();
	return 0;
}

简单题

ICPC 比赛中的签到题难度

P9827 [ICPC 2020 Shanghai R] Sky Garden

28042026

题意:\(n\leq500\) 个以原点为圆心,半径为 \(1, 2, ..., n\) 的圆,与 \(m\leq500\) 个平分圆的直线,只能沿着圆/线走,求在线与圆各个交点中,所有点对最小距离之和。

不知道为什么,我 \(O(n^3)\) 会 TLE。不过改成 \(O(n^2)\) 很容易,对于第一条边,枚举其他边上的每个点对答案的贡献即可。

注意 \(m = 1\) 时需要特判一下,查了半个小时才发现...

NOTE:注意考虑 n, m 较小时的特殊情况
#include <iostream>
#include <iomanip>
#include <math.h>
#include <vector>
 
int n, m;
const long double PI = acos(-1.0);
const long double IPS = 1e-8;
struct NODE {
	long double r, d;
};
std:: vector < NODE > a, b;
inline void kagari () {
	scanf("%d %d", &n, &m);
	const long double th = PI / (long double)m;
	long double ans = 0.0;
	for (int j = 1; j <= n; ++j) {
		long double d = th;
		for (int i = 2; i <= m * 2; ++i) {
			b.push_back({ (long double)j, d });
			d += th;
		}
		ans += (long double)(j * (j + 1));
	}
	for (int j = 0; j < b.size(); ++j) {
		// ans: R + 2r or R + r + (d-2)r
		// when d > 2 or d < 2	
		long double d = b[j].d;
		if (d - PI > IPS) d = PI * 2.0 - d;
		
		long double R = b[j].r * b[j].r, r = (long double)(b[j].r * (b[j].r + 1)) / 2.0;
		if (d - 2.0 > 0.0) ans += (R + r);
		else ans += (R + (d - 1.0) * r);
		
		r = b[j].r * ((long double)n - b[j].r), R = (long double)(n * (n + 1)) / 2.0 - (long double)(b[j].r * (b[j].r + 1)) / 2.0;
		if (d - 2.0 > 0.0) ans += R + r;
		else ans += R + (d - 1.0) * r;
	}
	ans = ans * (long double)m;
	if (m == 1) ans -= (long double)(n * (n + 1));
	std:: cout << std:: fixed << std:: setprecision(10) << ans;
	return;
}
int main () {
	kagari();
	return 0;
}

CPCC 河南 2023 J. Mocha 沉迷电子游戏

28042026

平面几何题。

画图,再分类讨论即可。

#include <stdio.h>
#include <math.h>

struct NODE {
	double x, y;
} A, B, C, P;

double dis (const NODE &x, const NODE &y) {
	return sqrt((x.x - y.x) * (x.x - y.x) + (x.y - y.y) * (x.y - y.y));
}

inline void kagari () {
	int x, y; scanf("%d %d", &x, &y); P.x = x, P.y = y;
	scanf("%d %d", &x, &y); A.x = x, A.y = y;
	scanf("%d %d", &x, &y); B.x = x, B.y = y;
	scanf("%d %d", &x, &y); double r = x * y;
	C.x = (A.x + B.x) / 2.0, C.y = (A.y + B.y) / 2.0;
	double h = dis(C, P), d = dis(A, P);
	double ans = 0.0;
	if (d < r) ans = acos(-1.0) * r * r;
	else if (h > r) {
		double a = acos(r / d);
		double b = 2.0 * acos(h / d);
		double c = acos(-1.0) * 2.0 - b - 2.0 * a;
		ans = h * dis(A, B) / 2.0 + r * sqrt(d * d - r * r) + c * r * r / 2.0;
	}
	else {
		ans = r * sqrt(d * d - r * r) * 2.0 + (acos(-1.0) * 2.0 - acos(r / d) * 4.0) * r * r / 2.0;
	}
	printf("%f\n", ans);
	return;
}
int main () {
	int t; scanf("%d", &t);
	while (t--) kagari();
	return 0;
}
posted @ 2026-04-28 15:42  dbg_8  阅读(21)  评论(0)    收藏  举报