公式

E - Closest Moment 解説 by yuto1115

解説

高橋君のスタート地点、ゴール地点、停止する時刻、青木君のスタート地点、ゴール地点、停止する時刻をそれぞれ \(A,B,p,C,D,q\) とおきます。すなわち、\(|\overrightarrow{AB}|=p, |\overrightarrow{CD}|=q\) です。一般性を失わず、\(p\geq q\) を仮定します。

時刻 \(q\) において高橋君がいる位置を \(E\) とおきます。すると、二人の行動は以下の \(2\) フェーズに分けて考えることができます。(なお、時刻 \([0, p]\) の範囲外について考える必要はありません。)

  • 時刻 \([0, q]\)(フェーズ \(1\)):高橋君が \(A\) から \(E\) へ、青木君が \(C\) から \(D\) へ向かって同時に出発し、同時に到着する。
  • 時刻 \([q, p]\)(フェーズ \(2\)):高橋君が \(E\) から \(B\) へ向かって歩く。青木君は \(D\) で停止している。

実は、それぞれのフェーズにおける二人の距離の最小値を求める問題は、二次元平面上における点と線分の距離を求める問題に帰着されます。フェーズ \(2\) については明らかなものの、 フェーズ \(1\) についてはやや非自明に思われるかもしれませんが、実際

\[ \begin{aligned} \min_{0\leq t\leq q} |(\overrightarrow{OA}+\frac{t}{q}\overrightarrow{AE})-(\overrightarrow{OC}+\frac{t}{q}\overrightarrow{CD})| &=\min_{0\leq t\leq q} |(\overrightarrow{OA}-\overrightarrow{OC})+\frac{t}{q}(\overrightarrow{AE}-\overrightarrow{CD})| \\ &=\min_{0\leq t\leq q} |\overrightarrow{CA}+\frac{t}{q}(\overrightarrow{AE}-\overrightarrow{CD})|\\ &=\min_{0\leq t\leq q} |\overrightarrow{OF}+\frac{t}{q}\overrightarrow{FG}| \end{aligned}\]

(ここで、点 \(F,G\) はそれぞれ \(\overrightarrow{OF}=\overrightarrow{CA}, \overrightarrow{FG}=\overrightarrow{AE}-\overrightarrow{CD}\) を満たす点)

より、原点と線分 \(FG\) の距離を求める問題に帰着されることから従います。

点と線分の距離は外積・内積等をうまく使って \(O(1)\) で求めることもできますが、本問題においては三分探索を用いても十分高速に動作します。

実装例 (C++) :

#include <bits/stdc++.h>

using namespace std;

using P = pair<int, int>;
using Pd = pair<double, double>;

// |a - b| ^ 2
int dist_sq(P a, P b) {
    int dx = a.first - b.first;
    int dy = a.second - b.second;
    return dx * dx + dy * dy;
}

// a - b
Pd sub(Pd a, Pd b) {
    return {a.first - b.first, a.second - b.second};
}

// |a - b|
double dist(Pd a, Pd b) {
    Pd d = sub(a, b);
    return sqrt(d.first * d.first + d.second * d.second);
}

// a + (b - a) * p
Pd internal_division(Pd a, Pd b, double p) {
    double x = a.first + (b.first - a.first) * p;
    double y = a.second + (b.second - a.second) * p;
    return {x, y};
}

const int NUM_ITERATION = 60;

// 線分 AB と原点との距離
// distance between the origin and segment AB
double dist_segment_and_origin(Pd a, Pd b) {
    auto f = [&](double p) {
        return dist(internal_division(a, b, p), {0, 0});
    };
    double l = 0, r = 1;
    for (int i = 0; i < NUM_ITERATION; i++) {
        double ml = (l * 2 + r) / 3;
        double mr = (l + r * 2) / 3;
        if (f(ml) < f(mr)) r = mr;
        else l = ml;
    }
    return f(l);
}

void solve() {
    vector<P> s(2), g(2);
    for (int i = 0; i < 2; i++) {
        cin >> s[i].first >> s[i].second >> g[i].first >> g[i].second;
    }
    if (dist_sq(s[0], g[0]) < dist_sq(s[1], g[1])) {
        swap(s[0], s[1]);
        swap(g[0], g[1]);
    }
    Pd ts = s[0], tg = g[0], as = s[1], ag = g[1];
    Pd tm = internal_division(ts, tg, dist(as, ag) / dist(ts, tg));
    double d1 = dist_segment_and_origin(sub(ts, as), sub(tm, ag));
    double d2 = dist_segment_and_origin(sub(tm, ag), sub(tg, ag));
    cout << min(d1, d2) << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    cout << fixed << setprecision(15);
    while (t--) solve();
}

投稿日時:
最終更新: