三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

CSP-S 2024 超速检测 题解

CSP-S 2024 超速检测 题解

CSP-S 2024 超速检测 题解

题目链接:CSP-S 2024 超速检测

题目简述

\(n\) 辆车,第 \(i\) 辆车初始位于 \(d_i\),速度为 \(v_i\),加速度为 \(a_i\)。道路范围为 \([0, L]\),有 \(m\) 个测速仪,位置分别为 \(p_1 < p_2 < \cdots < p_m\)。限速为 \(V\)

  • 第一问:有多少辆车会被至少一个测速仪检测到超速(即车辆经过测速仪时速度 \(> V\))?
  • 第二问:在保证仍能检测到所有超速车辆的前提下,最多可以关闭多少个测速仪?

算法思路

1. 物理模型 → 超速区间

对于第 \(i\) 辆车,运动学公式:

\(v^2 = v_i^2 + 2a_i (x - d_i)\)

其中 \(x\) 为车辆位置。要求 \(v > V\),即

\(v_i^2 + 2a_i (x - d_i) > V^2\)

整理得:

\(x > d_i + \frac{V^2 - v_i^2}{2a_i} \quad (a_i \neq 0)\)

由于测速仪位置是离散的,我们只需要知道车辆在哪些 连续位置区间 上会超速。设该区间为 \([l_i, r_i]\)(均为整数位置,包含边界)。

分类讨论:

  • \(v_i > V\):初始即超速。

    • \(a_i \ge 0\),速度不会减小,则从 \(d_i\)\(L\) 一直超速,即 \(l_i = d_i,\ r_i = L+1\)(用 \(L+1\) 表示无穷远)。
    • \(a_i < 0\),速度会逐渐减小,需要求出速度恰好降到 \(V\) 的位置 \(x_0\)
      \(x_0 = d_i + \frac{V^2 - v_i^2}{2a_i}\)
      因为 \(a_i < 0\),分母为负,\(x_0 < d_i\) 时可能不会降到限速以下?实际上,若初始超速且加速度为负,超速区间从 \(d_i\)\(\lfloor x_0 - 1 \rfloor\)(整数位置),代码中用整数防精度:
      \(r_i = d_i + \frac{v_i^2 - V^2 - 2a_i - 1}{-2a_i} - 1\)
      此处用整除向上取整的技巧,保证不出现浮点数。
  • \(v_i \le V\)

    • \(a_i \le 0\),速度不会增加,永远不会超速,\(l_i = r_i = L+1\)(空区间)。
    • \(a_i > 0\),速度会逐渐增加,超速从某个位置开始:
      \(x_0 = d_i + \frac{V^2 - v_i^2}{2a_i}\)
      则第一个超速的整数位置为 \(\lfloor x_0 \rfloor + 1\),即:
      \(l_i = d_i + \frac{V^2 - v_i^2}{2a_i} + 1\)
      之后一直超速到 \(L\)\(r_i = L+1\)

最终,每辆车对应一个超速区间 \([l_i, r_i]\)(闭区间),若 \(l_i > r_i\) 则无超速。


2. 判断是否被检测到

给定测速仪位置数组 \(p\),我们使用前缀和 pre[x] 表示位置 \(x\) 及以前有多少个测速仪。则区间 \([l, r]\) 内是否有测速仪,只需判断:

\(pre[r] - pre[l-1] > 0\)

若成立,说明该车会被至少一个测速仪拍到,计入第一问答案 cnta

同时,为了处理第二问,我们需要记录每个超速区间对应的“最右测速仪”。对于区间 \([l_i, r_i]\),利用二分查找 upper_bound(p+1, p+m+1, r_i) - p - 1 得到最后一个位置 \(\le r_i\) 的测速仪下标 pos。然后令:

\(arr[pos] = \max(arr[pos], l_i)\)

其中 arr[pos] 表示:若选择下标为 pos 的测速仪,它必须覆盖所有左端点 \(\ge arr[pos]\) 的区间(因为我们要让该测速仪尽可能向左覆盖,取最大左端点是最紧的要求)。


3. 贪心求最少保留测速仪

现在问题转化为:有若干个区间 \([l_i, r_i]\),每个区间已经绑定到其最右侧的测速仪 pos(即 \(p_{pos} \le r_i\)\(p_{pos}\) 是满足条件的最靠右的测速仪)。我们想要用尽量少的测速仪点覆盖所有区间,但这里测速仪只能选择这些离散点。

经典区间选点问题:按右端点排序,贪心选择右端点最小的区间的最右点。但代码采用从右向左的反向贪心,原理等价。

从右向左扫描测速仪下标 i(从 m2):

  • 当前测速仪 i 需要覆盖一些区间,这些区间的要求记录在 arr[i] 中(即这些区间的左端点最大值)。如果 arr[i] 有效(\(\ge 0\)),表示必须选点 i 才能覆盖这些区间。
  • 考虑左侧相邻的测速仪 i-1,位置为 \(p_{i-1}\)
    • \(p_{i-1} < arr[i]\),则左侧测速仪无法覆盖这些区间(因为 \(p_{i-1}\) 位于区间左端点左边),所以测速仪 i 必须保留。
    • 否则,左侧测速仪可以覆盖这些区间(因为 \(p_{i-1} \ge arr[i]\),且 \(p_{i-1} \le p_i \le r\),所以 \(p_{i-1}\) 一定在区间内),此时测速仪 i 可以被替代,我们将 arr[i] 的要求合并到 arr[i-1] 上:
      \(arr[i-1] = \max(arr[i-1], arr[i])\)
      并清空 arr[i](设为极小值)。

扫描结束后,所有 arr[i] \ge 0 的下标就是需要保留的测速仪数量 cntb。答案第二问为 m - cntb(可关闭的数量)。


代码实现细节

  • 使用 long long 避免乘法溢出。
  • 数组 pre 大小开至 M = 1e6+5,因为位置范围 \(0 \sim L\)
  • 计算区间时,全部使用整数运算,避免浮点数误差。
  • 区间端点 \(L+1\) 表示无限远,前缀和数组开到 \(L+2\)
  • arr 初始化为 -0x3f3f3f3f(极小值),代表无区间要求。
  • 二分查找使用 upper_bound,注意下标从 1 开始。

复杂度分析

  • 预处理每辆车:\(O(n)\)
  • 前缀和与差分:\(O(L + m)\)(实际 \(L\) 可能达 \(10^6\),可行)。
  • 每辆车二分查找最右测速仪:\(O(n \log m)\)
  • 扫描测速仪贪心:\(O(m)\)

总时间复杂度:\(O((n+m)\log m + L)\),空间复杂度:\(O(L + n + m)\)


完整代码(附注释)

#include <bits/stdc++.h>
#define int long long
using namespace std;const int N = 1e5 + 5, M = 1e6 + 5;int n, m, L, V;
int d[N], v[N], a[N], p[N];
int pre[M];          // 测速仪位置前缀和
int l[N], r[N];      // 每辆车的超速区间 [l, r]
int arr[N];          // arr[i]:测速仪 i 需要覆盖的最左端点
int cnta, cntb;signed main() {ios::sync_with_stdio(false);cin.tie(nullptr);int T;cin >> T;while (T--) {cin >> n >> m >> L >> V;memset(pre, 0, sizeof(pre));memset(arr, -0x3f, sizeof(arr));for (int i = 1; i <= n; i++) {cin >> d[i] >> v[i] >> a[i];}for (int i = 1; i <= m; i++) {cin >> p[i];pre[p[i]]++;}// 前缀和for (int i = 1; i <= L + 1; i++) {pre[i] += pre[i - 1];}// 计算每辆车的超速区间for (int i = 1; i <= n; i++) {if (v[i] > V) { // 初始超速l[i] = d[i];if (a[i] >= 0) { // 加速或匀速,一直超速r[i] = L + 1;} else { // 减速,计算降到 V 的位置int fs = d[i] + (v[i] * v[i] - V * V - 2 * a[i] - 1) / (-2 * a[i]);r[i] = min(L + 1ll, fs - 1);}continue;}// 初始不超速if (a[i] <= 0) { // 不会超速l[i] = r[i] = L + 1;continue;}// 加速,计算开始超速的位置int fs = d[i] + (V * V - v[i] * v[i]) / (2 * a[i]) + 1;l[i] = min(fs, L + 1ll);r[i] = L + 1;}cnta = cntb = 0;// 第一问 & 构建 arrfor (int i = 1; i <= n; i++) {if (l[i] > r[i]) continue;if (pre[r[i]] - pre[l[i] - 1] == 0) continue; // 区间内无测速仪cnta++; // 会被拍到// 找到区间内最靠右的测速仪下标int pos = upper_bound(p + 1, p + m + 1, r[i]) - p - 1;arr[pos] = max(arr[pos], l[i]);}// 贪心:从右向左合并for (int i = m; i >= 2; i--) {if (p[i - 1] < arr[i]) continue; // 左侧测速仪无法覆盖,保留当前// 可以合并arr[i - 1] = max(arr[i - 1], arr[i]);arr[i] = -0x3f3f3f3f; // 清空}for (int i = 1; i <= m; i++) {if (arr[i] >= 0) cntb++; // 需要保留的测速仪个数}cout << cnta << ' ' << m - cntb << '\n';}return 0;
}

总结

本题将物理运动学与经典的区间选点问题相结合,核心在于:

  1. 将超速条件转化为连续区间,并用整数运算避免精度问题。
  2. 利用前缀和快速判断区间内是否有测速仪
  3. 利用二分查找将区间绑定到最右侧的测速仪,再通过反向贪心求出最少保留数量。

本文由 AI 辅助整理

← 返回列表