NJPC2017: F - ダブルス

問題概要

数直線の原点上に人が2人いる。時刻Tiに座標Xiへボールが飛んでくることを意味するN個の整数の組(Ti, Xi)が与えられる。このとき2人のうちどちらか一人が時刻Tiに座標Xiにいるとボールをキャッチすることができる。2人の移動速度をvとしたとき、すべてのボールをキャッチできる最小のvを求めよ。

N <= 105

解法

二分探索をし、二分探索の中でDPをする。i個目のボールを処理した時点で、ボールをキャッチした方の人はXiにいて、キャッチしてない方の人はある連続した区間内のどこかにいる。この区間がなるべく広くなるようキャッチの担当を割り当てた方が得であるので、以下のようなDPが立つ。

dp(i): ボールiまでを処理したとき、ボールiをキャッチしていない方の人が存在しうる最大の区間

遷移は「i個目のボールをキャッチした人が(i+1)個目もキャッチする」「i個目のボールをキャッチしなかった人が(i+1)個目をキャッチする」の2通り。ボールをキャッチしに行く人が間に合うかどうかは、二分探索で速度を決め打っていることから計算可能である。ボールをキャッチしない方の人はその分左右に膨らむことができるのでそれに基づいて区間を求める。

感想

区間が連続になる が本質っぽい(むずい)

コード (D言語)

import std.stdio, std.array, std.string, std.conv, std.algorithm;
import std.typecons, std.range, std.random, std.math, std.container;
import std.numeric, std.bigint, core.bitop, std.bitmanip;

void main() {
    immutable real INF = 1L << 59;
    
    auto N = readln.chomp.to!int;
    auto T = new long[](N);
    auto X = new long[](N);
    foreach (i; 0..N) {
        auto s = readln.split.map!(to!long);
        T[i] = s[0];
        X[i] = s[1];
    }

    real hi = 10^^9;
    real lo = 0;
    foreach (_; 0..100) {
        real mid = (hi + lo) / 2;
        auto dp = new Tuple!(real, real)[](N+1);
        foreach (i; 0..N+1) dp[i] = tuple(INF, -INF);
        dp[0] = tuple(0.0L, 0.0L);
        real prev_t = 0;
        real prev_x = 0;
        foreach (i; 0..N) {
            if (dp[i][0] > dp[i][1])
                break;
            real t = T[i] - prev_t;
            real x = abs(X[i] - prev_x);
            if (x <= mid * t) {
                dp[i+1][0] = min(dp[i+1][0], dp[i][0] - t * mid);
                dp[i+1][1] = max(dp[i+1][1], dp[i][1] + t * mid);
            }
            if (dp[i][0] - t * mid <= X[i] && X[i] <= dp[i][1] + t * mid) {
                dp[i+1][0] = min(dp[i+1][0], prev_x - t * mid);
                dp[i+1][1] = max(dp[i+1][1], prev_x + t * mid);
            }
            prev_t = T[i];
            prev_x = X[i];
        }
        (dp[N][0] <= dp[N][1] ? hi : lo) = mid;
    }

    writefln("%.09f", hi);
}