UVa 1398 Meteor
题目描述
著名的韩国互联网公司 NHN\texttt{NHN}NHN 提供了一项基于互联网的照片服务,用户可以通过控制 NHN\texttt{NHN}NHN 拥有的高性能望远镜直接拍摄太空中的天文现象。几天后,将出现一场被认为是本世纪最大的流星雨。NHN\texttt{NHN}NHN 宣布举办一场摄影比赛,奖励使用 NHN\texttt{NHN}NHN 照片服务拍摄到包含最多流星照片的用户。为了这次比赛,NHN\texttt{NHN}NHN 提前在其网页上提供了流星的轨迹信息。获胜的最佳方法是计算望远镜能够捕捉到最多流星的时刻。
你有 nnn 颗流星,每颗都做匀速直线运动。流星 mim_imi 随时间 ttt 沿轨迹 pi+t×vip_i + t \times v_ipi+t×vi 运动,其中 ttt 是非负实数值,pip_ipi 是 mim_imi 的起点,viv_ivi 是 mim_imi 的速度。点 pi=(xi,yi)p_i = (x_i, y_i)pi=(xi,yi) 在 (X,Y)(X, Y)(X,Y) 平面中用 XXX 坐标 xix_ixi 和 YYY 坐标 yiy_iyi 表示,速度 vi=(ai,bi)v_i = (a_i, b_i)vi=(ai,bi) 是一个非零向量,在 (X,Y)(X, Y)(X,Y) 平面中有两个分量 aia_iai 和 bib_ibi。
望远镜有一个矩形框架,左下角为 (0,0)(0, 0)(0,0),右上角为 (w,h)(w, h)(w,h)。当流星位于框架内部(不在边界上)时,称该流星在望远镜框架中。你需要计算望远镜框架中流星数量最多的某个时间,并输出该最大流星数量。
输入格式
输入包含 TTT 个测试用例。第一行给出测试用例的数量 TTT。每个测试用例的第一行包含两个整数 www 和 hhh(1≤w,h≤100,0001 \leq w, h \leq 100,0001≤w,h≤100,000),表示望远镜框架的宽度和高度,用一个空格分隔。第二行包含一个整数 nnn,表示输入点(流星)的数量,1≤n≤100,0001 \leq n \leq 100,0001≤n≤100,000。接下来的 nnn 行每行包含四个整数 xix_ixi, yiy_iyi, aia_iai, bib_ibi;(xi,yi)(x_i, y_i)(xi,yi) 是起点 pip_ipi,(ai,bi)(a_i, b_i)(ai,bi) 是第 iii 颗流星的非零速度向量 viv_ivi;xix_ixi 和 yiy_iyi 是介于 −200,000-200,000−200,000 和 200,000200,000200,000 之间的整数值,aia_iai 和 bib_ibi 是介于 −10-10−10 和 101010 之间的整数值。注意 aia_iai 和 bib_ibi 中至少有一个不为零。这四个值用一个空格分隔。假设所有起点 pip_ipi 都是不同的。
输出格式
对于每个测试用例,输出望远镜框架中在某个时刻能够出现的流星的最大数量。
解题思路
问题分析
我们需要找到一个非负时间 ttt,使得在矩形 (0,0)(0,0)(0,0) 到 (w,h)(w,h)(w,h) 内部(不含边界)的流星数量最多。每颗流星做匀速直线运动,运动方程为:
(x,y)=(xi,yi)+t⋅(ai,bi),t≥0(x, y) = (x_i, y_i) + t \cdot (a_i, b_i), \quad t \ge 0(x,y)=(xi,yi)+t⋅(ai,bi),t≥0
关键思路
- 
时间区间计算:对于每颗流星,我们可以计算出它进入矩形内部的时间区间 [tstart,tend)[t_{\text{start}}, t_{\text{end}})[tstart,tend)。如果流星从不进入矩形内部,则该区间为空。
 - 
边界条件处理:由于边界不算内部,我们需要使用严格不等式来处理进入和离开的时刻。
 - 
扫描线算法:将所有进入事件 (t,+1)(t, +1)(t,+1) 和离开事件 (t,−1)(t, -1)(t,−1) 按时间排序,然后扫描这些事件,维护当前在矩形内部的流星数量,并记录最大值。
 
详细解题步骤
1. 计算单颗流星在矩形内部的时间区间
对于每颗流星,我们需要分别计算在 XXX 方向和 YYY 方向上的时间区间,然后取交集。
XXX 方向的时间区间:
- 
如果 ai=0a_i = 0ai=0:
- 如果 xi≤0x_i \le 0xi≤0 或 xi≥wx_i \ge wxi≥w:永远不在内部,区间为空
 - 否则:始终在内部,区间为 [0,∞)[0, \infty)[0,∞)
 
 - 
如果 ai>0a_i > 0ai>0:
- 进入内部时间:当 xi+t⋅ai>0x_i + t \cdot a_i > 0xi+t⋅ai>0,即 t>−xiait > \frac{-x_i}{a_i}t>ai−xi
 - 离开内部时间:当 xi+t⋅ai<wx_i + t \cdot a_i < wxi+t⋅ai<w,即 t<w−xiait < \frac{w - x_i}{a_i}t<aiw−xi
 - 因此区间为:(−xiai,w−xiai)\left( \frac{-x_i}{a_i}, \frac{w - x_i}{a_i} \right)(ai−xi,aiw−xi)
 
 - 
如果 ai<0a_i < 0ai<0:
- 进入内部时间:当 xi+t⋅ai<wx_i + t \cdot a_i < wxi+t⋅ai<w,即 t>w−xiait > \frac{w - x_i}{a_i}t>aiw−xi(注意 ai<0a_i < 0ai<0)
 - 离开内部时间:当 xi+t⋅ai>0x_i + t \cdot a_i > 0xi+t⋅ai>0,即 t<−xiait < \frac{-x_i}{a_i}t<ai−xi
 - 因此区间为:(w−xiai,−xiai)\left( \frac{w - x_i}{a_i}, \frac{-x_i}{a_i} \right)(aiw−xi,ai−xi)
 
 
YYY 方向的时间区间(同理):
- 
如果 bi=0b_i = 0bi=0:
- 如果 yi≤0y_i \le 0yi≤0 或 yi≥hy_i \ge hyi≥h:永远不在内部,区间为空
 - 否则:始终在内部,区间为 [0,∞)[0, \infty)[0,∞)
 
 - 
如果 bi>0b_i > 0bi>0:
- 区间为:(−yibi,h−yibi)\left( \frac{-y_i}{b_i}, \frac{h - y_i}{b_i} \right)(bi−yi,bih−yi)
 
 - 
如果 bi<0b_i < 0bi<0:
- 区间为:(h−yibi,−yibi)\left( \frac{h - y_i}{b_i}, \frac{-y_i}{b_i} \right)(bih−yi,bi−yi)
 
 
2. 处理时间区间
对于每颗流星:
- 计算 XXX 方向区间 [tx1,tx2)[t_{x1}, t_{x2})[tx1,tx2)
 - 计算 YYY 方向区间 [ty1,ty2)[t_{y1}, t_{y2})[ty1,ty2)
 - 取交集得到总区间 [max(tx1,ty1),min(tx2,ty2))[\max(t_{x1}, t_{y1}), \min(t_{x2}, t_{y2}))[max(tx1,ty1),min(tx2,ty2))
 - 如果开始时间小于 000,则设为 000(因为 t≥0t \ge 0t≥0)
 - 如果区间有效(开始时间小于结束时间),则创建两个事件:
- 进入事件:(tstart,+1)(t_{\text{start}}, +1)(tstart,+1)
 - 离开事件:(tend,−1)(t_{\text{end}}, -1)(tend,−1)
 
 
3. 扫描线算法
- 将所有事件按时间排序
 - 时间相同时,离开事件优先处理(避免多计数)
 - 扫描事件,维护当前流星数量,记录最大值
 
精度处理
由于输入是整数,使用 double\texttt{double}double 类型计算足够精确。使用一个小的 ϵ\epsilonϵ 值(如 10−910^{-9}10−9)来处理浮点精度问题。
参考代码
// Meteor
// UVa ID: 1398
// Verdict: Accepted
// Submission Date: 2025-11-01
// UVa Run Time: 0.030s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net#include <bits/stdc++.h>
using namespace std;const double EPS = 1e-9; // 精度控制// 事件结构体
struct Event {double time; // 事件发生时间int type;    // 事件类型:+1 表示进入,-1 表示离开Event(double t, int tp) : time(t), type(tp) {}// 排序规则:时间相同则离开事件优先bool operator<(const Event& other) const {if (fabs(time - other.time) < EPS) return type < other.type;return time < other.time;}
};int main() {ios_base::sync_with_stdio(false);cin.tie(nullptr);int testCaseCount;cin >> testCaseCount;while (testCaseCount--) {int width, height;cin >> width >> height;int meteorCount;cin >> meteorCount;vector<Event> events;for (int i = 0; i < meteorCount; ++i) {int startX, startY, velX, velY;cin >> startX >> startY >> velX >> velY;// 计算 X 方向的时间区间double xStartTime, xEndTime;if (velX > 0) {xStartTime = max(0.0, (-startX + EPS) / velX);xEndTime = (width - startX - EPS) / velX;} else if (velX < 0) {xStartTime = max(0.0, (width - startX + EPS) / velX);xEndTime = (-startX - EPS) / velX;} else { // velX == 0if (startX > 0 && startX < width) {xStartTime = 0.0;xEndTime = 1e20; // 表示无穷大} else {xStartTime = 1.0;xEndTime = 0.0; // 空区间}}// 计算 Y 方向的时间区间double yStartTime, yEndTime;if (velY > 0) {yStartTime = max(0.0, (-startY + EPS) / velY);yEndTime = (height - startY - EPS) / velY;} else if (velY < 0) {yStartTime = max(0.0, (height - startY + EPS) / velY);yEndTime = (-startY - EPS) / velY;} else { // velY == 0if (startY > 0 && startY < height) {yStartTime = 0.0;yEndTime = 1e20; // 表示无穷大} else {yStartTime = 1.0;yEndTime = 0.0; // 空区间}}// 取 X 和 Y 方向的交集double startTime = max(xStartTime, yStartTime);double endTime = min(xEndTime, yEndTime);// 如果区间有效,创建事件if (startTime < endTime - EPS) {events.emplace_back(startTime, 1);   // 进入事件events.emplace_back(endTime, -1);    // 离开事件}}// 按时间排序事件sort(events.begin(), events.end());int currentCount = 0;int maxCount = 0;// 扫描事件for (const auto& event : events) {currentCount += event.type;if (currentCount > maxCount) {maxCount = currentCount;}}cout << maxCount << "\n";}return 0;
}
