1 条题解
-
0
「PA 2018」Magiczne wieże 题解
问题分析
我们需要找到所有安全点构成的区域面积。
安全点的定义:从该点向任何方向移动,都会接近某个魔法师(在某个塔上)。
数学表达:点 是安全的,当且仅当对于任意方向向量 ,存在某个魔法塔 使得:
$$\frac{d}{dt} \text{dist}(P + t\vec{v}, T) < 0 \quad \text{当 } t \to 0^+ $$关键观察
这个问题等价于求所有魔法塔的 Voronoi 区域的交集。
Voronoi 图:平面被划分为多个区域,每个区域包含距离某个站点最近的所有点。
安全区域 = 对于任意点 ,存在某个魔法塔 ,使得 到 的距离小于等于到其他所有塔的距离。
算法思路
方法:半平面交
对于每对魔法塔 ,它们的垂直平分线将平面分成两个半平面:
- 距离 更近的半平面
- 距离 更近的半平面
安全区域就是所有"距离 更近"的半平面的交集。
步骤:
- 枚举每个魔法塔
- 对于每个 ,构建它相对于其他所有塔的最近半平面
- 求所有这些半平面的交集
- 计算交集多边形的面积
C++ 代码实现
#include <bits/stdc++.h> using namespace std; const double EPS = 1e-8; const double INF = 1e9; struct Point { double x, y; Point(double x = , double y = ) : x(x), y(y) {} Point operator + (const Point& p) const { return Point(x + p.x, y + p.y); } Point operator - (const Point& p) const { return Point(x - p.x, y - p.y); } Point operator * (double k) const { return Point(x * k, y * k); } Point operator / (double k) const { return Point(x / k, y / k); } double dot(const Point& p) const { return x * p.x + y * p.y; } double cross(const Point& p) const { return x * p.y - y * p.x; } double len2() const { return x * x + y * y; } double len() const { return sqrt(len2()); } Point rotate90() const { return Point(-y, x); } Point normalize() const { return *this / len(); } }; struct Line { Point p, v; Line(Point p = Point(), Point v = Point()) : p(p), v(v) {} // 点在有向线的左侧 bool onLeft(Point a) const { return v.cross(a - p) > EPS; } }; // 半平面交 vector<Point> halfplaneIntersection(vector<Line> lines) { // 按极角排序 sort(lines.begin(), lines.end(), [](const Line& a, const Line& b) { return atan2(a.v.y, a.v.x) < atan2(b.v.y, b.v.x); }); int n = lines.size(); vector<Line> q(n); vector<Point> ans; int l = , r = -1; for (int i = ; i < n; i++) { while (l < r && !lines[i].onLeft(ans[r - 1])) r--; while (l < r && !lines[i].onLeft(ans[l])) l++; if (l > r) { q[++r] = lines[i]; } else { if (abs(q[r].v.cross(lines[i].v)) < EPS) { if (q[r].v.dot(lines[i].v) > ) { if (!lines[i].onLeft(q[r].p)) { q[r] = lines[i]; } } else { return vector<Point>(); // 无解 } } else { q[++r] = lines[i]; } } if (l < r) { Point dir = q[r].v.rotate90(); double t = (q[r-1].p - q[r].p).cross(q[r-1].v) / dir.cross(q[r-1].v); ans.push_back(q[r].p + dir * t); } } while (l < r && !q[l].onLeft(ans[r - 1])) r--; if (r - l <= 1) return vector<Point>(); Point dir = q[l].v.rotate90(); double t = (q[r].p - q[l].p).cross(q[r].v) / dir.cross(q[r].v); ans.push_back(q[l].p + dir * t); return ans; } // 计算多边形面积 double polygonArea(const vector<Point>& poly) { double area = ; int n = poly.size(); for (int i = ; i < n; i++) { area += poly[i].cross(poly[(i + 1) % n]); } return abs(area) / 2.0; } int main() { ios::sync_with_stdio(false); cin.tie(); int n; cin >> n; vector<Point> towers; for (int i = ; i < n; i++) { double ax, ay, bx, by; cin >> ax >> ay >> bx >> by; towers.push_back(Point(ax, ay)); towers.push_back(Point(bx, by)); } int m = towers.size(); double ans = ; // 对每个塔,构建它的安全区域 for (int i = ; i < m; i++) { vector<Line> lines; // 添加边界(一个大正方形) lines.push_back(Line(Point(-1000, -1000), Point(1, ))); lines.push_back(Line(Point(1000, -1000), Point(, 1))); lines.push_back(Line(Point(1000, 1000), Point(-1, ))); lines.push_back(Line(Point(-1000, 1000), Point(, -1))); // 添加与其他塔的垂直平分线 for (int j = ; j < m; j++) { if (i == j) continue; Point mid = (towers[i] + towers[j]) / 2.0; Point dir = (towers[j] - towers[i]).rotate90(); lines.push_back(Line(mid, dir)); } vector<Point> safe_region = halfplaneIntersection(lines); if (!safe_region.empty()) { ans += polygonArea(safe_region); } } cout << fixed << setprecision(10) << ans << endl; return ; }算法正确性
定理:上述算法能正确计算安全区域的面积。
证明:
- 完备性:安全点必须位于某个塔的 Voronoi 区域内
- 正确性:半平面交精确地计算了 Voronoi 区域的交集
- 无重复:每个安全点只被一个塔的 Voronoi 区域包含
复杂度分析
- 外层循环:
- 内层循环: 构建半平面
- 半平面交:
- 总复杂度:,对于 可接受
优化版本
对于大规模数据,可以使用更高效的算法:
// 优化:预先计算所有垂直平分线 vector<Line> getAllBisectors(const vector<Point>& towers) { vector<Line> bisectors; int m = towers.size(); for (int i = ; i < m; i++) { for (int j = i + 1; j < m; j++) { Point mid = (towers[i] + towers[j]) / 2.0; Point dir = (towers[j] - towers[i]).rotate90(); bisectors.push_back(Line(mid, dir)); } } return bisectors; }算法标签
- 计算几何
- 几何图形的交与并
总结
本题的核心在于将安全区域问题转化为 Voronoi 图的交集问题,通过半平面交算法精确计算面积。虽然复杂度较高,但在给定的数据范围内完全可行。
- 1
信息
- ID
- 5612
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者