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
注意到当且仅当两条线有端点重合,或这两条线段重合时有答案,其余时候答案一定为 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}\))。在这里不给出证明了。
这时有两个情况:
- \(A\) 不在凸包上:
先随便在凸包上取一点作为 \(B\),在凸包上从 \(B\) 开始逆时针找到使 \(\overrightarrow{AB} \cdot \overrightarrow{AC} \leq 0\) 且最小的 \(C\)。然后逆时针枚举 \(B\),\(C\) 也只需要逆时针依次变化。这样双指针的方式,整体时间复杂度为 \(O(n^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;
}

浙公网安备 33010602011771号