跳转至

I - Kuririn MIRACLE

基本信息

题目出处2018 ICPC 亚洲区域赛青岛站网络赛
队伍通过率7/1550 (0.5%)

题解

记第一辆车的圆心为 \(A\),第二辆车(障碍物)的圆心为 \(B\)。首先容易想到答案的上界:圆 \(A\) 就跟在圆 \(B\) 后面沿 \(x\) 轴走到终点,时间是 \(\frac{d}{v}\)

由于圆 \(A\) 的速度比圆 \(B\) 快,也可以先让圆 \(A\) 与圆 \(B\) 保持相切,从圆 \(B\) 上方绕过去,绕到某个角度后,再沿直线走到终点。接下来我们分别计算两段路径的时间。

第一段路径

\(A\) 的第一段运动可以拆分成跟着圆 \(B\) 的,速度为 \(v\) 的匀速直线运动,和绕着圆 \(B\) 的,半径为 \(2r\) 的变速圆周运动。

i.png

如上图所示,记 \(AB\)\(x\) 轴负方向的夹角为 \(\theta\),点 \(A\) 相对点 \(B\) 的切向速度为 \(v_t(\theta)\)\(v_x(\theta)\)\(v_t(\theta)\) 沿 \(x\) 轴正方向的分解速度,\(v_y(\theta)\)\(v_t(\theta)\) 沿 \(y\) 轴正方向的分解速度,有以下方程组。

\[ \begin{cases} v_x(\theta) = v_t(\theta)\sin \theta \\ v_y(\theta) = v_t(\theta)\cos \theta \\ (v_x(\theta) + v) ^ 2 + (v_y(\theta))^2 = (2v)^2 \end{cases} \]

解得 \(v_t(\theta) = v (\sqrt{\sin^2\theta + 3} - \sin\theta)\)

设第一段运动一直持续到 \(\theta = \alpha\),则根据弧长公式,第一段运动的用时为

\[ t = \int_{0}^{\alpha} \frac{2rd\theta}{v_t(\theta)} \]

这个积分没有代数解,可以使用 自适应辛普森公式 近似计算。

第二段路径

当点 \(A\) 经过第一段路径后,坐标为

\[ \begin{cases} x = vt + 2r - 2r\cos \alpha \\ y = 2r\sin \alpha \end{cases} \]

接下来,点 \(A\) 将从这个坐标向终点做匀速直线运动,因此第二段运动的用时为 \(\frac{\sqrt{(x - d)^2 + y^2}}{2v}\)

剩下的问题就是如何确定 \(\alpha\) 的值。设第二段路径开始前,点 \(A\) 和终点的连线与 \(x\) 轴负方向的夹角为 \(\beta\)。考虑以点 \(B\) 为中心的参考系,点 \(A\) 在该参考系中将从 \(\vec{a} = (x - (2r + vt), y)\) 出发,沿 \(\vec{w} = (2v\cos \beta - v, -2v\sin \beta)\) 方向做匀速直线运动。为了保证 \(AB\) 两点之间的距离不缩小,向量 \(\vec{a}\)\(\vec{w}\) 之间的夹角不能超过 \(90\) 度,即 \(\vec{a} \cdot \vec{w} \ge 0\)。二分 \(\alpha\) 的值并检查即可。

复杂度是二分的复杂度乘以辛普森公式的复杂度。

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <bits/stdc++.h>
#define EPS 1e-9
using namespace std;

double v, r, d;

int sgn(double x) {
    if (x > EPS) return 1;
    if (x < -EPS) return -1;
    return 0;
}

// --------------------
//  辛普森公式
// --------------------

double f(double x) {
    double s = sin(x);
    return 1 / (sqrt(s * s + 3) - s);
}

double simpson(double l, double r) {
    double mid = (l + r) / 2;
    return (r - l) * (f(l) + 4 * f(mid) + f(r)) / 6;
}

double calc(double l, double r, double eps, double ans) {
    double mid = (l + r) / 2;
    double fl = simpson(l, mid), fr = simpson(mid, r);
    if (abs(fl + fr - ans) <= 15 * eps) return fl + fr + (fl + fr - ans) / 15;
    return calc(l, mid, eps / 2, fl) + calc(mid, r, eps / 2, fr);
}

// --------------------
//  辛普森公式结束
// --------------------

void solve() {
    scanf("%lf%lf%lf", &v, &r, &d);

    // 直接跟在圆 B 后面走到终点
    double ans = d / v;
    const double PI = acos(-1);
    // 二分第一段运动结束时的角度
    double head = PI / 2, tail = PI;
    while (tail - head > EPS) {
        double mid = (head + tail) / 2;
        double t = 2 * r / v * calc(0, mid, EPS, simpson(0, mid));
        double x = 2 * r * (1 - cos(mid)) + v * t;
        double y = 2 * r * sin(mid);
        double beta = atan2(y, d - x);
        // 计算点乘的结果是否非负
        double dot = (x - (2 * r + v * t)) * (2 * v * cos(beta) - v) - y * (2 * v * sin(beta));
        if (sgn(dot) >= 0) {
            tail = mid;
            ans = min(ans, t + sqrt((x - d) * (x - d) + y * y) / (2 * v));
        } else {
            head = mid;
        }
    }
    printf("%.9f\n", ans);
}

int main() {
    int tcase; scanf("%d", &tcase);
    while(tcase--) solve();
    return 0;
}