2023年海淀区中小学信息学竞赛复赛(小学组试题第七题 赛车游戏(car))

一、先看原题:


二、题意理解:两辆小车跑向对方!

想象一条长长的跑道------像操场的直线赛道。

  • 陶陶的小车 从左边起点(0)往右开

  • 天天的小车 从右边终点(l)往左开

  • 两人 速度都从 1 开始

赛道上有几个"加速点"(像踩到蘑菇就加速的道具😄)

例如:
a = {3, 7, 10}

表示在 3 米、7 米、10 米有加速带。

🚗💨 小车只要经过一个加速带,就立刻速度 +1!

📌 问题:两辆车什么时候第一次碰头?


三、这道题其实是"时间 + 位置"的问题

两车一直在移动(速度可能变),我们要找它们 第一次位置相同的时间

如果速度不变,这很简单:

剩余距离 ÷(两车速度之和)

但问题是:

速度会不断变化!

因为车会不断踩到加速带。

这就不能用简单公式了。


四、为什么不能"每一秒模拟一下"?

你可能会想:

我把时间一秒一秒模拟,两车同时走......

但这样会 TLE(超时失败),因为:

  • 赛道长度 l 最多到 10⁹

  • 一秒走一点点,要模拟十亿次

    → 🚫 根本不可能!

所以我们不能按"秒"模拟。


五、正确做法:按"事件"模拟(关键思想!)

1、事件是什么?

👉 就是 小车到达加速带的时刻

2、为什么按事件模拟就可以?

因为:

  • 两车速度不变的时候,它们是匀速直线运动

  • 在这段时间里,不发生任何特别的事情

  • 真正需要关注的,是"什么时候某辆车经过加速带"


六、整个算法就 3 步!(超级重要)

🚦 第 1 步:找下一次"事件"

左车下个加速带位置:a[i]

右车下个加速带位置:a[j]

左车到达自己的加速带需要时间:

(a[i] - 左车位置) / 左车速度

右边一样。

取二者中较小的,就是下一次事件的时间段。


🚗 第 2 步:把两辆车往前"蹭"时间 dt

dt 时间里:

  • 左车前进 vl * dt

  • 右车前进 vr * dt

  • 记录累计时间增加


💥 第 3 步:处理事件

如果左车到达了加速点:

  • 它的速度 +1

  • i + +(指向下一个加速点)

如果右车到达加速点:

  • 它的速度 +1

  • j - -


🌟 第 4 步:这么反复做,直到:

左车和右车之间已经没有加速点了(i > j)

此时就只剩:

剩余距离 ÷(vl + vr)

加到答案里,结束!


七、示例:

1、我们手动跑题目样例(第二组)

题目样例输入的第二组是:

1 10

1

也就是:

  • n = 1

  • l = 10

  • 加速带只有一个:a = {1}

速度初始:左车 = 1,右车 = 1

左车从 0 → 右

右车从 10 → 左

👇 我们进行"事件模拟"。


🏎️ 第 1 段:左右车奔向加速带 1

左车到加速带 1:

(1 - 0) / 1 = 1 秒

右车到加速带 1:

(10 - 1) / 1 = 9 秒

👉 左车先到!

所以第一个事件:dt = 1 秒


🕒(1 秒后)

左车走到 1

右车走到 9

位置:

L = 1 R = 9

速度变化:

  • 左车到达加速带 → 速度 1 → 2

  • 右车没到 → 速度仍是 1

累计时间:

T = 1


🏎️ 第 2 段:没有加速带了 → 直接算相遇时间

现在的状态:

  • 左车位置:1

  • 右车位置:9

  • 距离:9 - 1 = 8

  • 左车速度:2

  • 右车速度:1

相对速度:

2 + 1 = 3

相遇还需要时间:

8 / 3 = 2.666666666666667


🏁 最终总时间:

第一段 1 秒

第二段 8/3 秒 = 2.666666666666667 秒


总计 = 1 + 2.666666666666667 = 3.666666666666667

✨ 这是最终结果!


八、参考程序:

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int main() {

    int T;
    cin >> T;
    cout << fixed << setprecision(15);

    while (T--) {
        int n;
        long long l;
        cin >> n >> l;
        vector<long long> a(n);
        for (int i = 0; i < n; ++i) cin >> a[i];

        int i = 0, j = n - 1;          // i 指向左车的下一个加速带,j 指向右车的下一个加速带
        double vl = 1.0, vr = 1.0;     // 左车和右车当前速度
        double posL = 0.0, posR = (double)l; // 当前两车位置
        double ans = 0.0;

        while (i <= j) {
            // 左车到下一个加速带的时间(如果没有下一个就设为大)
            double tLeft = (a[i] - posL) / vl;
            // 右车到下一个加速带的时间
            double tRight = (posR - a[j]) / vr;

            double dt = min(tLeft, tRight);
            // 两车同时前进 dt 时间
            posL += vl * dt;
            posR -= vr * dt;
            ans += dt;

            // 处理到达加速带的车
            if (abs(posL - a[i]) < 1e-12) { // 左车到达左边的加速带
                vl += 1.0;
                i++;
            }
            if (abs(posR - a[j]) < 1e-12) { // 右车到达右边的加速带
                vr += 1.0;
                j--;
            }
        }

        // 现在 i > j,说明中间没有未处理的加速带,计算剩余距离
        double rem = posR - posL;
        if (rem > 0) {
            ans += rem / (vl + vr);
        }
        cout << ans << "\n";
    }
    return 0;
}

九、初学者特别注意事项(超级关键)

✅ 1. 不能用按秒来模拟

因为模拟的次数太多,会超时。

✅ 2. 必须按事件来模拟

事件 = 某辆车到达加速点

只要处理 n 个事件即可。

✅ 3. 浮点数比较用 abs(x - y) < 1e-12

因为 double 有误差,不能直接用 ==。

✅ 4. 输出要足够多小数位

题目要求误差 ≤ 1e-6

我们输出 15 位绝对没问题。


十、小结:

你可以把小车当作:

  • 在平地走的时候速度不变,脑子不用管

  • 只有踩到加速点才会改变速度,所以那一刻是"重要事件"

就像你走路:

  • 一段时间你的速度一直是一样的(不吃东西、不喝水)

  • 突然你吃了糖→速度快了

  • 突然你背重了→变慢了

  • 你只需记录"变化点"

  • 不需要记录每一秒你在干啥

所以我们只处理速度变化的瞬间。

这就是"事件驱动模拟"的核心。

相关推荐
长安er2 小时前
LeetCode 11盛最多水的容器 & LeetCode 42接雨水-双指针2
面试·力扣·双指针·接雨水
Azxcc02 小时前
c++ core guidelines解析--让接口易于使用
开发语言·c++
helloworddm2 小时前
NSIS编写C/C++扩展
c语言·开发语言·c++
ULTRA??2 小时前
QT向量类实现GJK碰撞检测算法3d版本
c++·qt·算法
煤球王子2 小时前
学而时习之:C++ 中的文件处理
c++
天赐学c语言2 小时前
12.10 - 合并两个有序链表 && 对字节对齐的理解
数据结构·c++·leetcode·链表
仰泳的熊猫3 小时前
1092 To Buy or Not to Buy
数据结构·c++·算法·pat考试
CSDN_RTKLIB3 小时前
解除vcpkg对VS的全局配置注入
c++
君义_noip3 小时前
信息学奥赛一本通 4017:【GESP2309三级】小杨的储蓄 | 洛谷 B3867 [GESP202309 三级] 小杨的储蓄
c++·算法·gesp·信息学奥赛