F - Minimize Bounding Square Editorial by evima

別解

入力される座標の最大値を \(M\) とします。求めるべき値に対して二分探索を行い、「辺の長さが \(S\) の正方形を描いてすべての点を収めることができるか?」という問題を \(O(\log M)\) 回解きます。

\(x\) 座標のみに注目すると以下の問題を得ます。

(問題)数直線上の座標 \(x_1, \dots, x_N\)\(N\) 個の点がある。閉区間 \([l, l+S]\) を選んで \(N\) 個の点すべてをその中に移動させる(元の問題と同じ操作が使える)。これに必要な最小の合計費用を求めよ。

この問題を \(x\) 座標・\(y\) 座標について個別に解き、答えの合計が \(K\) 以下であれば冒頭の問題の答えは Yes、そうでなければ No です。

あとは、\(l\) を適切に選ぶのみです。最適な選択は、\(2N\) 個の値 \(x_1, x_2, \dots, x_N, x_1 - S, x_2 - S, \dots, x_N - S\) を昇順に並べた際の \(N\) 番目の値です(正確には、\(N\) 番目と \(N+1\) 番目の値の間であれば何でも構いません)。これは、横軸に \(l\)、縦軸に合計費用をプロットして得られる折れ線グラフの傾きの変化を考えれば示せます(十分小さい \(l\) での傾きは \(-N\) で、\(l\) を大きくしていくと上記の \(2N\) 個の値で \(1\) ずつ増加し、傾き \(0\) に対応する点が最適です)。

事前に \(x_1, x_2, \dots, x_N\) をソートしておけば、「\(x_1, x_2, \dots, x_N, x_1 - S, x_2 - S, \dots, x_N - S\) を昇順に並べた際の \(N\) 番目の値」はマージソートの要領で \(O(N)\) 時間で求められるため(C++ の std::merge や Python の heapq.merge が使えます)、元の問題を \(O(N (\log N + \log M))\) 時間で解くことができます。(速い言語であれば、\(2N\) 個の値を毎回 \(O(N \log N)\) 時間でソートして全体で \(O(N \log N \log M)\) 時間かけても何とか間に合うでしょう。)

実装例(Python)

import heapq

N, K = map(int, input().split())
X, Y = [], []
for _ in range(N):
    x, y = map(int, input().split())
    X.append(x)
    Y.append(y)
X.sort()
Y.sort()


def cost(z, l):
    p = list(heapq.merge(z, map(lambda x: x - l, z)))[N]
    c = 0
    for x in z:
        if x < p:
            c += p - x
        elif x > p + l:
            c += x - (p + l)
    return c


lo, hi = -1, 10**9
while hi - lo > 1:
    mi = (lo + hi) // 2
    if cost(X, mi) + cost(Y, mi) <= K:
        hi = mi
    else:
        lo = mi
print(hi)

posted:
last update: