P11232 [CSP-S 2024] 超速检测
难度:普及+/提高。
考点:二分、贪心。
题意:
题意较长,没有题目大意,否则你也大意。
-
主干道长度为 L L L,有 n n n 辆车,看做左端点为 0 0 0,第 i i i 辆距离左端点处 d i d_i di 处驶入,初速度为 v 0 i v_{0_{i}} v0i,加速度为 a i a_i ai,当瞬间速度为 0 0 0 / 到达右端点 L L L 时驶离主干道。
-
m m m 个测试仪,第 j j j 个测试仪位于右端点 P j P_j Pj 处,每个测试仪可以启动/关闭。某辆车如果在启动的测速仪上瞬间速度超过了限速 V V V,则说明超速了,注意起点和终点都有可能会有测速仪,(有可能驶入主干道就超速了)。
题目所求:
第一问:如果 全部测速仪启动, n n n 辆车中会有多少辆车超速了。
第二问:如果想关闭一部分测试仪,且 不漏掉记录超速的车辆情况下,最多可以关闭多少个测速仪。
贴心的准备了加速度相关的公式(虽然只有一个有用);
分析:
Subtask 1、2: n , m ≤ 20 n,m \le 20 n,m≤20。
暴力枚举,每个测速仪开和关的状态,共 2 m 2^m 2m 种选择。再用 m × n m \times n m×n 来检查答案,但 2 20 × n × m ≤ 4 e 8 2^{20} \times n \times m \le 4e8 220×n×m≤4e8。尝试将 n × m n \times m n×m 优化到 O ( n ) O(n) O(n)。
分类讨论:
其中 p j p_j pj 表示车 i i i 在主干道上「首个」测速仪的位置, p m p_m pm 表示主干道最后一个测速仪的位置。
- 匀速:最大速度 [ p j , p m ] [p_j, p_m] [pj,pm] 一样,从头到尾速度不变,所以判断区间内任意一个测速仪都可以。
- 加速:最大速度在测速仪 p m p_m pm 位置,由于是匀加速运动,则整段路中最后一个测速仪时速度值最大,如果没有超速,那么主干道上都没超速。
- 减速:最大速度在测速仪 p j p_j pj 位置,由于是匀减速运动,则整段路中首个遇到的测速仪速度值最大。
通过二分来加速查找 p j p_j pj 「首个」测速仪的位置。
int j = std::lower_bound( p.begin(), p.end(), d[i] ) - p.begin();
时间复杂度优化到 O ( n l o g m ) ∗ 2 m O(nlogm)*2^{m} O(nlogm)∗2m, 20 p t s 20pts 20pts 到手。
Subtask 3、4:性质 A A A, a i = 0 a_i=0 ai=0(匀速运动), 20 p t s 20 pts 20pts。
非常 e a s y easy easy,直接判断 p j ∼ p m p_j \sim p_m pj∼pm 其中一个测速仪即可,不妨选择 p m p_m pm,如果 p m ≥ d i p_m \ge d_i pm≥di && v i > V v_i > V vi>V。说明车辆 i i i 在整段路都超速了,答案就是 m − j + 1 m-j+1 m−j+1 测速仪。
正解( 100 p t s 100pts 100pts ):包含 性质 B 、 C B、C B、C。
对于上一个问题进行深入分析:什么样的测速仪能检测车 i i i 超速。
不妨设 f i f_i fi:表示车 i i i 进入主干道「首个」测速仪的下标。
- 匀速, [ f i , m ] [f_i,m] [fi,m],所有碰到的测速仪都可以检测到。
- 加速, [ f i , m ] [f_i,m] [fi,m] 的后缀,如上图,匀加速运动,相当于一整段主干道速度都在提升,有可能是加速一段之后「瞬时速度」大于 V V V 碰到首个摄像头 P k 1 P_{k1} Pk1,由于 P k 1 ≥ f i P_{k1} \ge f_i Pk1≥fi 故这一段肯定属于 [ f i , m ] [f_i,m] [fi,m] 的「后缀」,实际可以表示为 [ p k 1 , m ] [p_{k1},m] [pk1,m]。
- 减速, [ f i , m ] [f_i,m] [fi,m] 的前缀,如上图,与加速相反,有可能一开始已经在超速,经过一段时间的减速后「瞬时速度」小于 V V V 碰到首个摄像头 P k 2 Pk_2 Pk2,由于 P k 2 ≤ m P_{k2} \le m Pk2≤m 故这一段肯定属于 [ f i , m ] [f_i,m] [fi,m] 的「前缀」,实际可以表示为 [ f i , p k 2 ] [f_i,p_{k2}] [fi,pk2]。
可以发现 符合要求 的是 连续的一段区间 [ f i , m ] [f_i,m] [fi,m],(符合要求):拍摄到超速测速仪,也好求**(二分)**求「前缀 / 后缀」。
{ 前缀,最后看到减速后不超速的点 后缀,最后看到加速后超速的点 \left\{ \begin{array}{lc} 前缀,最后看到减速后不超速的点\\ 后缀,最后看到加速后超速的点\\ \end{array} \right. {前缀,最后看到减速后不超速的点后缀,最后看到加速后超速的点
二分的细节:check
第 i i i 车走 j j j 测速仪有无超速。
可以发现题目中给了几个公式,但实际上有用的只有一个:瞬时速度: v 0 2 + 2 × a × s \sqrt{ v_0^2 + 2 \times a \times s } v02+2×a×s。
二分就是 check
每辆车 i i i 在(初始速度为 V 0 V_0 V0,距离起点为 d i d_i di,加速度为 a i a_i ai)的瞬时速度: V 0 2 + 2 × a × ( p j − d i ) > V \sqrt{ V_0^2 + 2 \times a \times( p_j-d_i ) } > V V02+2×a×(pj−di)>V 。
根号会出现误差,最直接的办法就是想办法出掉精度丢失上带来的误差,直接两边平方, V 0 2 + 2 × a × ( p j − d i ) > V 2 V_0^2 + 2 \times a \times (p_j-d_i) > V^2 V02+2×a×(pj−di)>V2。
还要计算一下答案是否会超出 int
, 1 0 6 + 2 ∗ 1 0 9 ≤ 2147483647 ( i n t ) 10^6 + 2*10^9 \le 2147483647(int) 106+2∗109≤2147483647(int)。
(如上图所示,答案就是一段一段连续的区间,代表某辆车被检测到超速的测速仪区间)
问 1 1 1:区间的个数(超速车的数量)。
问 2 2 2:保留若干个点,便得每个区间至少有一个点。
第二个问题就很眼熟是吧(入门贪心:区间选点问题?),我们可以用(贪心) → \rightarrow → (区间的右端点排序),如下图。
作为
OI
教练一般不刻意压行,应该代码分块展示,会让 读者/学生 思路更加清晰。
参考代码:
#include <bits/stdc++.h>
#define ll long longconst int N = 1e5 + 10;
int n, m, L, V, d[N], v[N], a[N];
std::vector<int> p; // 寄存测速仪
struct Seg
{int l, r; // 记录车 i 会被测速仪,检测到超速的区间 [l,r]bool operator<(const Seg &a) // 贪心 ::: 右端点排序{return r < a.r;}
};
std::vector<Seg> seg;// 计算瞬间速度
ll speed(ll v, ll a, ll s)
{return v * v + 2 * a * s; // 题目已经给出式子
}void handler(int d, int v, int a)
{// first 表示进入主干道后第一个碰到的测速仪(下标)int first = std::lower_bound(p.begin(), p.end(), d) - p.begin();// 进入主干道 ::: 没有遇到测速仪,跳过if (first >= m)return;// 匀速 ::: 那一开始不超速意味着永远不会超速if (a == 0){if (v > V)seg.push_back({first, m - 1});return;}// 分类二分int l = first, r = m - 1, ans = -1, mid;// 减速,找到「最后一个」检测到超速的测速仪if (a < 0){while (l <= r){mid = l + r >> 1;if (speed(v, a, p[mid] - d) > V * V)ans = mid, l = mid + 1;elser = mid - 1;}if (ans != -1)seg.push_back({first, ans});return;}// 加速,找到「首个」能检测到加速的测速仪if (a > 0){while (l <= r){mid = l + r >> 1;if (speed(v, a, p[mid] - d) > V * V)ans = mid, r = mid - 1;elsel = mid + 1;}if (ans != -1)seg.push_back({ans, m - 1});}
}void solve()
{p.clear(); // T 组数组记得初始化seg.clear();std::cin >> n >> m >> L >> V;for (int i = 1; i <= n; i++)std::cin >> d[i] >> v[i] >> a[i]; // 起点, 初始速度, 加速度for (int i = 1, x; i <= m; i++)std::cin >> x, p.push_back(x); // pi 测试仪的位置for (int i = 1; i <= n; i++) // 处理出 ::: 每辆车 i 的测速仪超速的区间handler(d[i], v[i], a[i]);// 贪心 ::: 区间覆盖问题啦!sort(seg.begin(), seg.end());int last = -1, ans = 0;for (auto it : seg)if (last < p[it.l]) // 贪心的每次 ::: 选择不重合区间中的右端点last = p[it.r], ans++;std::cout << (int)seg.size() << " " << m - ans << "\n";
}int main()
{std::ios::sync_with_stdio(false), std::cin.tie(nullptr);int T;std::cin >> T;while (T--)solve();return 0;
}