AIGC标识 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(从 m 到 2):

  • 当前测速仪 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;

        // 第一问 & 构建 arr
        for (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 辅助整理

posted @ 2026-08-12 12:14  Void__114  阅读(33)  评论(0)    收藏  举报