一、先看原题:


二、题意理解:两辆小车跑向对方!
想象一条长长的跑道------像操场的直线赛道。
-
陶陶的小车 从左边起点(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 位绝对没问题。
十、小结:
你可以把小车当作:
在平地走的时候速度不变,脑子不用管
只有踩到加速点才会改变速度,所以那一刻是"重要事件"
就像你走路:
一段时间你的速度一直是一样的(不吃东西、不喝水)
突然你吃了糖→速度快了
突然你背重了→变慢了
你只需记录"变化点"
不需要记录每一秒你在干啥