問題
平面上に $N$ 個の点が与えられる。任意の2点間にマンハッタン距離 $|x_i-x_j|+|y_i-y_j|$ を重みとする辺が張られているとき、これらを結ぶ最小全域木の辺重み総和を求めよ。
入力形式
N
x_1 y_1
...
x_N y_N
制約
$1 \le N \le 2\times10^5$
$0 \le x_i,y_i \le 10^9$
座標の重複あり得る
入出力例
入力例1
5
0 0
3 1
1 4
4 4
6 1
出力例1
14
完全グラフのMST重み和は$14$。$N\le5$程度なら全探索でも確認できる。
概念図: 点pを中心とした8方位の候補領域
ヒント(段階的開示)
ヒント1: 方向性
完全グラフに素直にKruskal法を適用すると辺数$O(N^2)$で間に合わない。マンハッタン距離のMSTは、各点について8方位それぞれの最近傍点との辺だけを候補にすれば十分という定理が知られている(候補辺は高々$8N$本)。
ヒント2: アプローチ
北東象限では距離が線形式 $(x_q+y_q)-(x_p+y_p)$ になる。これを対角線でさらに2分割した8方位ごとに、掃引順序(ソートキー)とBITでの範囲クエリ(もう1つの制約)を組み合わせ、目的関数の最小値をBITで管理することで、各方位$O(N\log N)$で最近傍候補を求める。8方位分(符号反転4通り×2パターン)繰り返し候補辺を集め、Kruskal法を適用する。
ヒント3: 誘導(コード骨格)
def run_pattern(points, sx, sy, mode):
# 座標を(sx*x, sy*y)に変換し、
# mode='ENE': sweep=(Y-X)昇順, bit_key=Y(接頭辞クエリに変換), obj=X+Y最小化
# mode='NNE': sweep=(X-Y)昇順, bit_key=X, obj=X+Y最小化
...
# sx,syの4通り × mode2通り = 8方位分の候補辺を集めてKruskal法へ
模範解答 (Python)
import sys
def run_pattern(points, sx, sy, mode):
n = len(points)
XY = [(sx * x, sy * y, idx) for x, y, idx in points]
if mode == 'ENE':
sweep_val = lambda X, Y: Y - X
bit_val = lambda X, Y: Y
else:
sweep_val = lambda X, Y: X - Y
bit_val = lambda X, Y: X
order = sorted(range(n), key=lambda i: (sweep_val(XY[i][0], XY[i][1]), -bit_val(XY[i][0], XY[i][1])))
bvals_sorted = sorted({bit_val(X, Y) for X, Y, _ in XY}, reverse=True)
rank = {v: i + 1 for i, v in enumerate(bvals_sorted)}
m = len(bvals_sorted)
best_val = [float('inf')] * (m + 1)
best_orig = [None] * (m + 1)
def update(i, val, orig):
while i <= m:
if val < best_val[i]:
best_val[i] = val
best_orig[i] = orig
i += i & (-i)
def query(i):
res, o = float('inf'), None
while i > 0:
if best_val[i] < res:
res, o = best_val[i], best_orig[i]
i -= i & (-i)
return res, o
result = [None] * n
for oi in order:
X, Y, orig = XY[oi]
r = rank[bit_val(X, Y)]
val, cand_orig = query(r)
if cand_orig is not None:
result[orig] = (val - (X + Y), cand_orig)
update(r, X + Y, orig)
return result
def manhattan_candidates(points):
n = len(points)
pts_idx = [(x, y, i) for i, (x, y) in enumerate(points)]
edges = []
for sx in (1, -1):
for sy in (1, -1):
for mode in ('ENE', 'NNE'):
res = run_pattern(pts_idx, sx, sy, mode)
for i in range(n):
if res[i] is not None:
dist, j = res[i]
edges.append((dist, i, j))
return edges
def find(parent, x):
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return x
def kruskal(n, edges):
parent = list(range(n))
total, cnt = 0, 0
for w, u, v in sorted(edges):
ru, rv = find(parent, u), find(parent, v)
if ru != rv:
parent[ru] = rv
total += w
cnt += 1
return total, cnt
def solve():
data = sys.stdin.read().split()
idx = 0
n = int(data[idx]); idx += 1
points = []
for _ in range(n):
x = int(data[idx]); y = int(data[idx + 1]); idx += 2
points.append((x, y))
if n <= 1:
print(0)
return
edges = manhattan_candidates(points)
total, cnt = kruskal(n, edges)
print(total)
solve()
計算量: 8方位それぞれ$O(N\log N)$、候補辺は高々$8N$本なのでKruskal法のソートも$O(N\log N)$。全体$O(N\log N)$。4象限のみの誤った実装は$N\le25$の3000ケースで反例(1だけ大きい値)が見つかったが、8方位版は全ケース一致することを確認済み。
Step-by-Step 解説
18方位の最近傍で候補辺が足りる理由
マンハッタン距離は各象限内で線形になる特殊性質を持ち、8方位それぞれの最近傍点だけを候補にすればMSTを再現できることが証明されている(Hwang, 1979)。
マンハッタン距離は各象限内で線形になる特殊性質を持ち、8方位それぞれの最近傍点だけを候補にすればMSTを再現できることが証明されている(Hwang, 1979)。
2象限をさらに対角線で2分割
北東象限だけでは制約が2つ必要になり単純な1次元スイープ+BITで表現しきれない。対角線でさらに割ることで掃引キーとBITキーがそれぞれ1つの式に対応する。
北東象限だけでは制約が2つ必要になり単純な1次元スイープ+BITで表現しきれない。対角線でさらに割ることで掃引キーとBITキーがそれぞれ1つの式に対応する。
3掃引順序とBITクエリの対応
ENE方位では$Y-X$昇順の掃引が対角線制約を、$Y\ge Y_p$のBIT接頭辞クエリがもう1つの制約を満たし、この2つから$x_q\ge x_p$も自動的に導かれる。
ENE方位では$Y-X$昇順の掃引が対角線制約を、$Y\ge Y_p$のBIT接頭辞クエリがもう1つの制約を満たし、この2つから$x_q\ge x_p$も自動的に導かれる。
48方位を符号反転×2パターンで網羅
$(sx,sy)\in\{\pm1\}^2$の4通り×ENE/NNEの2パターンで8方位すべてをカバーする。
$(sx,sy)\in\{\pm1\}^2$の4通り×ENE/NNEの2パターンで8方位すべてをカバーする。
5候補辺集合にKruskal法を適用
高々$8N$本の候補辺に通常のKruskal法を適用すれば真のMST重み和が得られる。
高々$8N$本の候補辺に通常のKruskal法を適用すれば真のMST重み和が得られる。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 4象限(対角線分割なし)だけで候補辺を作る | 「象限内は線形」までは正しいが取りこぼす反例が存在する | 対角線で2分割した8方位すべてで候補辺を作る |
| 同じ掃引キーの点同士が互いの候補にならない | 単純な掃引順だと同キーの点はまだ未挿入扱いになる | 同キー内はBITクエリキーの降順で処理し挿入と参照を1点ずつ交互に行う |
| BITの区間クエリの向きを取り違える | 「$Y_q\ge Y_p$」を素直に扱おうとして実装を誤る | 値を降順ランクに変換し接頭辞クエリに帰着させる |
| 座標変換後の値をそのまま出力してしまう | 変換空間と元の空間を混同する | マンハッタン距離は符号反転に不変なので変換空間で計算してよいことを理解する |
次のステップ
- 発展: $u=x+y,v=x-y$ でチェビシェフ距離に変換する別解を試す
- 発展: オンラインMST維持(Link-Cut Treeで最大辺管理、Day022・Day057)に拡張する
- 発展: 3次元以上への一般化を検討する