マンハッタン距離
$u = x + y$, $v = x - y$ と変換する(45 度回転して $\sqrt{2}$ 倍する)と、 $\lvert a \rvert + \lvert b \rvert = \max(\lvert a + b \rvert, \lvert a - b \rvert)$ から $\lvert x_1 - x_2 \rvert + \lvert y_1 - y_2 \rvert = \max(\lvert u_1 - u_2 \rvert, \lvert v_1 - v_2 \rvert)$ が成り立ちます。マンハッタン距離が $u$ の差と $v$ の差の大きい方になるので、 $u$ と $v$ を別々に扱えます。たとえば最も遠い 2 点の距離は、 $u$ の最大値と最小値の差と、 $v$ の最大値と最小値の差のうち大きい方です。
最小全域木は、各点 $i$ から見て 45 度ずつに区切った 8 つの範囲それぞれで、 $i$ に最も近い点と $i$ を結んだ辺だけから作れます。同じ範囲にある 2 点 $j$, $k$ の距離は、 $i$ から $j$ への距離と $i$ から $k$ への距離の大きい方以下になるからです。辺に向きはないので 4 つの範囲を見れば足ります。座標を入れ替えたり符号を反転したりして同じ形の範囲にし、 $x$ 座標の大きい順に見ながら Fenwick Tree で最も近い点を探します。
manhattan_farthest(ps): 点(タプル(x, y))のリスト(1 個以上)のうちマンハッタン距離が最も大きい 2 点について、(距離, i, j)を返す( $i$, $j$ は点の番号)。 $O(N)$manhattan_mst(ps): 点のリストについて、マンハッタン距離の最小全域木に使われうる辺の候補を(w, i, j)( $w$ は点 $i$ と点 $j$ の距離)のリストで返す。辺は $4N$ 本以下。 $O(N \log N)$
manhattan_mst が返した辺だけで最小全域木を求めると、すべての 2 点の間に辺がある完全グラフの最小全域木になります。 $O(N^2)$ の Prim 法より速いです。
例(最小全域木(Kruskal・Prim) の kruskal を使う):
ps = [(0, 0), (3, 1), (1, 4), (5, 5)]
print(manhattan_farthest(ps)) # (10, 3, 0)
es = manhattan_mst(ps)
cost, used = kruskal(len(ps), [(i, j, w) for w, i, j in es])
print(cost) # 14
tree = [es[k] for k in used] # 最小全域木の辺 (w, i, j)
コード
def manhattan_farthest(ps):
# マンハッタン距離が最大の 2 点の (距離, i, j)(i, j は点の番号。点は 1 個以上)
# |x1 - x2| + |y1 - y2| = max(|u1 - u2|, |v1 - v2|)(u = x + y, v = x - y)
n = len(ps)
best = (-1, 0, 0)
for s in (1, -1):
a = [x + s * y for x, y in ps]
i = max(range(n), key=a.__getitem__)
j = min(range(n), key=a.__getitem__)
if a[i] - a[j] > best[0]:
best = (a[i] - a[j], i, j)
return best
def manhattan_mst(ps):
# マンハッタン距離の最小全域木に使われうる辺 (w, i, j) のリスト(4N 本以下)
n = len(ps)
xs = [x for x, y in ps]
ys = [y for x, y in ps]
INF = float("inf")
res = []
for k in range(4):
# X[j] >= X[i] かつ Y[j] - X[j] >= Y[i] - X[i] となる j(i から見て 45 度から 90 度の範囲)のうち、
# X[j] + Y[j] が最小のものと i を結ぶ。X の大きい順に見て、Y - X の順の BIT で最小値を求める
keys = sorted(set(y - x for x, y in zip(xs, ys)), reverse=True)
rank = {v: r + 1 for r, v in enumerate(keys)} # Y - X が大きいほど小さい番号
m = len(keys)
bv = [INF] * (m + 1)
bi = [-1] * (m + 1)
for i in sorted(range(n), key=lambda i: (xs[i], ys[i]), reverse=True):
r = rank[ys[i] - xs[i]]
s = xs[i] + ys[i]
best, j = INF, -1
t = r
while t:
if bv[t] < best:
best, j = bv[t], bi[t]
t -= t & -t
if j != -1:
res.append((best - s, i, j))
t = r
while t <= m:
if s < bv[t]:
bv[t], bi[t] = s, i
t += t & -t
# (x, y) -> (y, x) -> (-y, x) -> (x, -y) と変換して 4 つの範囲を見る
if k % 2 == 0:
xs, ys = ys, xs
else:
xs = [-x for x in xs]
return res