题解:P12535 [XJTUPC 2025] 棱堡
itzxianfish · · 题解
前言
本蒟蒻当时在比赛现场猜测到了这道题跟凸包有关系,但是奈何当时根本不会求凸包,遂遗憾抛弃本题 QAQ。
后来学了 Graham 算法和搓了几道板子之后回想起来这道题,结果用 Graham 调了一个下午,疯狂保龄。
因为直到我 AC 这道题的时候,仍然没有题解,于是在大佬的帮助以及各方面调查下,加上现学了一手 Andrew 算法,然后一发了本题,于是写此篇题解以提供帮助和警示后人。
题解
题意
判断简单非退化多边形上是否存在有限个
第一个比较有意思的点就是,这里的的多边形指整个多边形区域,所以你的线段是不能穿过内部的,参考测试样例的第二个点。
4
1 1
-1 1
-1 -1
1 -1
显然,这是一个正方形。
但是如果你误解了题意,可能就会认为线段从内部穿过是没问题的,于是浪费时间在思考这件事上面。
与凸包的联系
在题目中给的图比划一下,大致猜测到棱堡的几何性质会与多边形的凹凸有关系。
这里有一个结论:如果一个简单非退化多边形是棱堡,则原多边形不存在任意一条边在凸包上。
简单证明如下。
我们假设存在一个棱堡的某一条边在多边形点集形成的凸包上。
那么根据凸包的定义,结合下图。
其中黄色加上线段
由于
但是此时我们如果取线段
我们发现
到这里,我们就简单的证明了结论:如果一个简单非退化多边形是棱堡,则原多边形不存在任意一条边在凸包上。
凸包算法
我们常见的凸包算法是 Graham 算法和 Andrew 算法,不熟悉的可以先去写一下模板题:P2742 [USACO5.1] 圈奶牛Fencing the Cows /【模板】二维凸包。
但是正如我的前言所说,我用 Graham 算法调了一个下午,所以我们这里解释一下 Graham 算法在这题里面的坑点。
Graham 算法坑点
我们知道 Graham 算法是基于极角排序的,在极角相同,即共线时,我们的第二关键字会选择向量模长。
但这个时候就会有个问题:这个共线点,他到底是不是在凸包上?
如图。
有
现在考虑
上面的分析其实聚焦于一个关键点就是:极角排序使用模长作为第二关键字处理共线情况会存在不确定性。
再看 Andrew 算法,排序只需要关注坐标的情况,涉及到共线的操作只有出栈,所以此时我们只需要略微调整模板,使得最终栈内能存在共线点即可。
综上,本题我们采用 Andrew 算法计算凸包。
注意事项
我们需要的是点集中所有在凸包上的点,如同前文所说,要把
AC Code
含注释。
#include <cstdio>
#include <algorithm>
#include <set>
#define int long long // 十年 IO 一场空,不开 ________ 见祖宗
using namespace std;
const int MAXN = 100020;
int t, n, top, used[MAXN], stk[MAXN];
// 快读
inline int read() {
int x = 0, f = 1; char c = getchar();
while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); }
while (c >= '0' && c <= '9') { x = (x << 1) + (x << 3) + (c ^ '0'); c = getchar(); }
return x * f;
}
// 向量,含叉乘
struct Vector {
int x, y;
Vector(int __x = 0, int __y = 0) : x(__x), y(__y) {}
int cross(const Vector& o) const { return x * o.y - y * o.x; }
};
// 点,重载减法获取向量,重载小于用于排序和 set
struct Point {
int x, y;
Point(int __x = 0, int __y = 0) : x(__x), y(__y) {}
Vector operator- (const Point& o) const { return Vector(x - o.x, y - o.y); }
bool operator< (const Point& o) const { return x == o.x ? y < o.y : x < o.x; }
} a[MAXN], b[MAXN];
// 凸包点集
set< Point > s;
// Andrew 算法
inline void Andrew() {
// 初始化
s.clear();
top = 0;
for (int i = 1; i <= n; i++)
used[i] = 0;
sort(a + 1, a + n + 1);
stk[++top] = 1;
for (int i = 2; i <= n; i++) {
while (top >= 2) {
Vector v1 = a[stk[top]] - a[stk[top - 1]];
Vector v2 = a[i] - a[stk[top]];
if (v1.cross(v2) < 0) // 注意符号
used[stk[top--]] = 0;
else
break;
}
used[i] = 1;
stk[++top] = i;
}
int t = top;
for (int i = n - 1; i; i--) {
if (!used[i]) {
while (top > t) {
Vector v1 = a[stk[top]] - a[stk[top - 1]];
Vector v2 = a[i] - a[stk[top]];
if (v1.cross(v2) < 0) // 注意符号
used[stk[top--]] = 0;
else
break;
}
used[i] = 1;
stk[++top] = i;
}
}
// 存入 set
for (int i = 1; i < top; i++)
s.insert(a[stk[i]]);
}
signed main() {
t = read();
while (t--) {
n = read();
for (int i = 1; i <= n; i++) {
a[i].x = read(), a[i].y = read();
b[i] = a[i]; // 保存原顺序
}
Andrew();
bool flag = true;
for (int i = 1; i <= n; i++)
if (s.count(b[i]) && s.count(b[i % n + 1])) // 题目已经保证输入顺序,直接判断相邻两个元素即可
flag = false;
puts(flag ? "YES" : "NO");
}
return 0;
}
后记
代码调了一个下午真的红温了,为了避免后人红温,留此解 awa。
关于使用 set,相信各位巨佬应该有时间更优的手段可供替换。
码字不易 TAT。
最后,管理员大大求过求过求过求过求过求过。