1 条题解
-
0
题意简述
输入一个由 个点( 为奇数)组成的多边形,表示一座“山”。
山谷是位于奇数下标(1-based)且不是第一个和最后一个的点,即 且 为奇数。
在直线 上放置点光源,要求每个山谷与至少一个点光源的连线不穿过山体内部。
求最少需要多少个点光源。
关键观察
山谷位置 因为 ,所以山谷是纵坐标的局部极小点(除了首尾),即 。
照亮条件 从点光源 到山谷 的线段不能与山体内部相交。 山体由连续折线构成,因此该线段不能与任何非相邻的山体边相交。
几何转化 对于某个山谷 ,能照亮它的点光源 必须满足:
从 向左右两侧的“山脊”连线,与直线 相交,得到一个区间 ,表示能照亮此山谷的 坐标范围。
这个区间可以通过从 向左和向右作射线,与 相交得到。 向左时,射线不能越过左边相邻的顶点;向右时,不能越过右边相邻的顶点。 实际上,由于山形是连续的相邻点交替上下,所以向左的射线会碰到左侧的“山脊”边,向右同理。
区间推导
设山谷 。
向左:连接 与左侧第一个比它高的顶点 ,延长到 ,得到左边界 。
向右:连接 与右侧第一个比它高的顶点 ,延长到 ,得到右边界 。
推导公式(相似三角形):
L i
x i − ( h − y i ) ( x i − x i − 1 ) y i − 1 − y i L i =x i − y i−1 −y i
(h−y i )(x i −x i−1 )
R i
x i + ( h − y i ) ( x i + 1 − x i ) y i + 1 − y i R i =x i + y i+1 −y i
(h−y i )(x i+1 −x i )
注意:因为 且 ,分母为正。
问题转化
每个山谷对应一个区间 ,我们要在 上选最少的点,使得每个区间至少包含一个点。
这就是经典的区间覆盖问题(点覆盖区间): 给定若干区间,求最少需要多少个点,使得每个区间至少包含一个点。
贪心解法:
按 排序。
遍历区间,如果当前区间不包含已选的点,则在该区间的右端点放一个点。
算法步骤
找出所有山谷位置 。
对每个山谷计算 。
按 排序。
贪心选择点。
时间复杂度
计算区间
排序
贪心 总 ,可过 。
示例代码(框架)
cpp #include <bits/stdc++.h> using namespace std;
struct Interval { double L, R; bool operator<(const Interval& other) const { return R < other.R; } };
int main() { int n, h; scanf("%d%d", &n, &h); vector x(n), y(n); for (int i = 0; i < n; i++) { scanf("%lf%lf", &x[i], &y[i]); }
vector<Interval> intervals; for (int i = 2; i < n - 1; i += 2) { // 山谷下标(0-based):2,4,6,...,n-3 double L = x[i] - (h - y[i]) * (x[i] - x[i-1]) / (y[i-1] - y[i]); double R = x[i] + (h - y[i]) * (x[i+1] - x[i]) / (y[i+1] - y[i]); intervals.push_back({L, R}); } sort(intervals.begin(), intervals.end()); int ans = 0; double last = -1e18; for (auto& inv : intervals) { if (inv.L > last) { ans++; last = inv.R; } } printf("%d\n", ans); return 0;}
总结
本题核心是将照亮条件转化为区间覆盖问题:
确定每个山谷的可见光源区间。
贪心求解最少的点覆盖所有区间。
- 1
信息
- ID
- 5207
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者