1 条题解
-
0
这是一个关于 计算几何 中 闵可夫斯基和 (Minkowski Sum) 的经典应用题。
题目分析
1. 问题转化
题目给定两个点集 (部落1)和 (部落2)。部落 的所有点移动向量 后,变为 。 如果在移动后,两个部落的凸包(Convex Hull)有交集,则判定为发生冲突。
两个凸包 和 有交集,等价于存在点 同时属于两个凸包:
即存在 和 ,使得:
移项得:
这意味着,如果向量 可以表示为 中的一个点减去 中的一个点,那么就存在冲突。 令集合 。 根据闵可夫斯基和的定义, 实际上就是 与 (即 关于原点对称后的图形)的闵可夫斯基和:
结论:我们要判断 是否会导致冲突,只需要判断点 是否在 凸多边形 的内部(或边界上)。
2. 闵可夫斯基和的性质与构造
- 凸性:两个凸多边形的闵可夫斯基和仍然是凸多边形。
- 顶点与边:新凸多边形的边是由原两个多边形的边,按照极角排序后首尾相接构成的。
- 构造算法:
- 求出点集 的凸包 。
- 求出点集 的凸包 ,并将所有坐标取反得到 (或者在计算时直接将 的点取反求凸包)。
- 将 和 的边提取出来。
- 将所有边按照极角大小进行归并排序。
- 从一个起始点(通常是 的最低点加上 的最低点)开始,依次加上排序后的边向量,即可得到 的所有顶点。
3. 询问处理
构造出凸多边形 后,对于每个询问 ,问题转化为 判定点是否在凸多边形内。 这可以通过二分查找在 的时间内完成( 为 的顶点数,)。
代码详解
这份代码使用了非常规范的计算几何模板。以下是核心步骤的解析:
1. 基础几何模板
代码前半部分定义了
Point和Line结构体,以及常用的向量运算:叉积 (cross)、点积 (dot)、点在直线左侧判定 (point_on_line_left) 等。这是解决几何问题的基石。2. 求凸包 (
convex_hull)函数
convex_hull使用了 Monotone Chain (单调链) 算法。- 先对点进行排序(x坐标优先,y坐标次之)。
- 分别构建下凸壳 (
l) 和上凸壳 (h)。 - 最终合并得到逆时针顺序的凸包顶点。
3. 构造闵可夫斯基和 (核心逻辑
solve函数)-
输入处理:
std::vector<Point<int>> a(n); // ... 输入 a ... std::vector<Point<int>> b(m); for (int i = 0; i < m; i++) { std::cin >> b[i].x >> b[i].y; b[i] *= -1; // 关键:将 B 的坐标取反,即求 A + (-B) }这里直接将 的坐标取反,实现了将“差”转化为“和”的处理。
-
求凸包:
ha = convex_hull(a),hb = convex_hull(b)。 -
提取并合并边:
// 提取边向量 for (int i = 0; i < int(ha.size()); i++) edges.push_back(ha[(i + 1) % ha.size()] - ha[i]); for (int i = 0; i < int(hb.size()); i++) edges.push_back(hb[(i + 1) % hb.size()] - hb[i]); // 极角排序 (归并) std::ranges::inplace_merge(..., [&](Point<int> p, Point<int> q) -> bool { // 使用叉积和象限判断进行极角比较 // ... });这里将两个凸包的边向量放在一起,按照角度进行排序。由于输入的凸包边本身就是有序的,使用
inplace_merge可以高效地(线性时间)完成归并。 -
生成新多边形:
std::vector hull = {ha[0] + hb[0]}; // 起点为两个凸包起点的和 for (auto e : edges ...) { hull.push_back(hull.back() + e); // 依次累加边向量 }最终得到的
hull就是 和 的闵可夫斯基和。
4. 处理询问 (点在凸多边形内判定)
对于每个询问 :
- 特判:先检查是否在 的第一条边对应的线段上。
- 二分查找:
这里利用凸多边形的性质,以int i = std::ranges::partition_point(hull | std::views::drop(1), [&](Point<int> p) -> bool { return !point_on_line_left<i64>(d, {p, hull[0]}); }) - hull.begin();hull[0]为极点,二分找到点 所在的角区域(扇形区域)。 - 最终判定:
判断 是否在这个扇形区域对应的外侧边的内侧(或边界上)。
!point_on_line_left<i64>(d, {hull[i], hull[i - 1]})用于判定点是否在边的右侧(因为代码中凸包是逆时针的,如果在左侧则是外部,右侧或共线则是内部)。注意代码逻辑最后的输出是1表示冲突(在内部),0表示不冲突。
复杂度分析
- 求凸包: 。
- 闵可夫斯基和: 边的归并排序是线性的(因为原凸包边已有序),耗时 。
- 查询: 共有 次查询,每次二分耗时 。
- 总时间复杂度: 。
鉴于数据范围 ,这个复杂度完全足以通过题目。
- 1
信息
- ID
- 6050
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者