🙌

ABC 183 | B - Billiards

2020/11/16に公開

問題

https://atcoder.jp/contests/abc183/tasks/abc183_b

考えたこと

S_x = G_xのときは真下に玉をぶつければいいのでS_xが答えである。

次にS_x != G_xのときを考える。G_x > S_xのときはGSを交換すればいいのでG_x < S_xのみのときを考えればいい。

G_x < S_xのときは以下のようにx軸上の点とS, Gのなす角\thetaが同じになる。

上記のときの点をmidと置くと以下のようになる。

midG_xからS_xまで二分探索で求める事を考える。\frac{S_y}{S_x-mid} < \frac{G_y}{mid - G_x}ならばmidが目的の場所より左にある。そうでなければ右にある。このようにして誤差10^{-6}以下になるまで求めればいい。100回くらいで十分もとまる。もっと少なくても求まるが計算量に対した影響はないので100回としている。

コード

実装時のTips

  • 出力で小数点12桁まで出すためにcout << fixed << std::setprecision(12)を利用している
  • 結果が小数点のときもあるのでlong doubleを使っている
#include <bits/stdc++.h>

#include <atcoder/all>

using namespace std;
using namespace atcoder;
using ll = long long;
using ld = long double;
using uint = unsigned int;
using ull = unsigned long long;
const int MOD = 1e9 + 7;

int main() {
  cout << fixed << std::setprecision(12);
  ld sx, sy, gx, gy;
  cin >> sx >> sy >> gx >> gy;
  if (sx == gx) {
    cout << sx << endl;
    return 0;
  }

  if (sx < gx) {
    swap(sx, gx);
    swap(sy, gy);
  }

  ld left = gx;
  ld right = sx;
  ll cnt = 100;
  while (cnt) {
    ld mid = (left + right) / 2;
    ld a = sy / (sx - mid);
    ld b = gy / (mid - gx);
    if (a < b) {
      left = mid;
    } else {
      right = mid;
    }
    cnt--;
  }
  cout << left << endl;
  return 0;
}

別解

二分探索をしていたが以下のように等号で置き換えmidを直接求めればいいということに気づいた。

\frac{S_y}{S_x-mid} = \frac{G_y}{mid - G_x}
mid=\frac{S_xG_y + G_xS_y}{S_y + G_y}

よってコードは以下のようになる。

#include <bits/stdc++.h>

#include <atcoder/all>

using namespace std;
using namespace atcoder;
using ll = long long;
using ld = long double;
using uint = unsigned int;
using ull = unsigned long long;
const int MOD = 1e9 + 7;

int main() {
  cout << fixed << std::setprecision(12);
  ld sx, sy, gx, gy;
  cin >> sx >> sy >> gx >> gy;
  if (sx == gx) {
    cout << sx << endl;
    return 0;
  }

  if (sx < gx) {
    swap(sx, gx);
    swap(sy, gy);
  }

  ld ans = (sx * gy + gx * sy) / (sy + gy);
  cout << ans << endl;
  return 0;
}

参考

Discussion