计算几何做题(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
若对圆取上下左右四个点,对三角形、正方形直接取节点,求凸包后,对凸包内所有点求直线距离,则仅有以下两种情况会计算错误的:
- 圆弧,这时会求成直线的长度
- 三角形上顶点与圆上顶点相连时,不是一条直线而是直线+圆弧
记录凸包中每个点的类型,便可找到这两种情况。第一种情况的正确长度固定为 \(\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\) 的情况。

浙公网安备 33010602011771号