始めて三分探索した。次書くときは黄金分割探索っての使ってみたい。

E - マッサージチェア2015

問題

$H \times W$の長方形内に3点配置したときの、点と点の距離の最小値の最大値を求めよ。

解法

1点が角に位置し、残りの2点は異なる辺上に位置するのは明らかである。 また、点と点の距離の最小値は、ある点の辺上の位置に関して凸な関数になっていることが分かる。 三分探索すればよい。 二分探索は単調なものにしか適用できないことに注意。

実装

#include <iostream>
#include <cstdio>
#include <complex>
#define repeat_from(i,m,n) for (int i = (m); (i) < (n); ++(i))
#define repeat(i,n) repeat_from(i,0,n)
using namespace std;
constexpr double eps = 1e-8;
double dist(double h, double w, double y, double x) {
    complex<double> p = { h, w };
    complex<double> q = { y, 0 };
    complex<double> r = { 0, x };
    return min(abs(p - q), min(abs(q - r), abs(r - p)));
}
double bar(int h, int w, double y) {
    double xl = 0, xr = w;
    while (xr - xl >= eps) {
        double x1 = (2 * xl + xr) / 3;
        double x2 = (xl + 2 * xr) / 3;
        double d1 = dist(h, w, y, x1);
        double d2 = dist(h, w, y, x2);
        if (d1 < d2) {
            xl = x1;
        } else {
            xr = x2;
        }
    }
    return dist(h, w, y, (xl + xr) / 2);
}
double foo(int h, int w) {
    double yl = 0, yr = h;
    while (yr - yl >= eps) {
        double y1 = (2 * yl + yr) / 3;
        double y2 = (yl + 2 * yr) / 3;
        double d1 = bar(h, w, y1);
        double d2 = bar(h, w, y2);
        if (d1 < d2) {
            yl = y1;
        } else {
            yr = y2;
        }
    }
    return bar(h, w, (yl + yr) / 2);
}
int main() {
    int datasets; cin >> datasets;
    repeat (dataset, datasets) {
        int h, w; cin >> h >> w;
        printf("%.16lf\n", foo(h, w));
    }
    return 0;
}