問題
$N$ 頂点の森(初期は孤立点)に対して $Q$ クエリを処理せよ。
- クエリ1:
1 u v w— 辺 $(u,v)$ を重み $w$ で追加(異なる成分間のみ) - クエリ2:
2 t— 頂点 $t$ が属する木の直径を出力
制約
$1 \le N \le 10^5$
$1 \le Q \le 10^5$
$1 \le w \le 10^9$
時間制限: 4sec / メモリ: 512MB
入出力例
入力例 1
4 5
1 1 2 3
1 2 3 5
2 1
1 3 4 2
2 4
出力例 1
8
8
概念図: 木の合体と直径更新
ヒント(段階的開示)
ヒント1: 方向性
Union-Find で各連結成分の「直径の両端点」を管理し、合体時に 4 通りの端点ペア距離を計算して最大を選ぶ。2 点間距離は BFS で計算。
ヒント2: 直径更新の法則
2本の木を辺でつなぐと新しい直径 = max(旧直径1, 旧直径2, 木1の端点aから木2の端点aへの距離, ...) の最大。木1の端点 (a1,b1)、木2の端点 (a2,b2) の 4 組み合わせを全試行。
ヒント3: 実装骨格
# dia[root] = (diameter, endpoint_a, endpoint_b)
def union(u, v, w_edge):
ru, rv = find(u), find(v)
du, au, bu = dia[ru]
dv, av, bv = dia[rv]
adj[u].append((v, w_edge)); adj[v].append((u, w_edge))
candidates = [(du, au, bu), (dv, av, bv)]
for a in [au, bu]:
for b in [av, bv]:
d = bfs_dist(a, b)
candidates.append((d, a, b))
best = max(candidates, key=lambda x: x[0])
# Union-Find 合体
dia[new_root] = best
模範解答 (Python)
import sys
from collections import defaultdict, deque
input = sys.stdin.readline
def solve():
N, Q = map(int, input().split())
parent = list(range(N+1))
rank_uf = [0] * (N+1)
dia = [(0, i, i) for i in range(N+1)]
adj = defaultdict(list)
def find(x):
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return x
def bfs_dist(s, t):
if s == t: return 0
dist = {s: 0}
dq = deque([s])
while dq:
v = dq.popleft()
for w, wt in adj[v]:
if w not in dist:
dist[w] = dist[v] + wt
if w == t: return dist[w]
dq.append(w)
return 0
def union(u, v, w_edge):
ru, rv = find(u), find(v)
if ru == rv: return
du, au, bu = dia[ru]
dv, av, bv = dia[rv]
adj[u].append((v, w_edge))
adj[v].append((u, w_edge))
candidates = [(du, au, bu), (dv, av, bv)]
for a in [au, bu]:
for b in [av, bv]:
d = bfs_dist(a, b)
candidates.append((d, a, b))
best = max(candidates, key=lambda x: x[0])
if rank_uf[ru] < rank_uf[rv]:
ru, rv = rv, ru
parent[rv] = ru
if rank_uf[ru] == rank_uf[rv]:
rank_uf[ru] += 1
dia[ru] = best
out = []
for _ in range(Q):
line = list(map(int, input().split()))
if line[0] == 1:
_, u, v, w = line
union(u, v, w)
else:
_, t = line
rt = find(t)
out.append(dia[rt][0])
print('\n'.join(map(str, out)))
solve()
Step-by-Step 解説
1直径の性質
木の直径は「最遠 2 頂点間のパス長」。2 本の木を辺でつなぐと新しい直径は旧直径 1、旧直径 2、または「木 1 の直径端点から木 2 の直径端点へのパス」のいずれかが最大。
木の直径は「最遠 2 頂点間のパス長」。2 本の木を辺でつなぐと新しい直径は旧直径 1、旧直径 2、または「木 1 の直径端点から木 2 の直径端点へのパス」のいずれかが最大。
2Union-Find 拡張
各連結成分の根に (diameter, endpoint_a, endpoint_b) を記録。Union 時に 4 通りの端点ペア距離を計算して最大を選ぶ。
各連結成分の根に (diameter, endpoint_a, endpoint_b) を記録。Union 時に 4 通りの端点ペア距離を計算して最大を選ぶ。
32 点間距離の BFS
動的グラフなので adjacency list に辺を追加しながら BFS で距離計算。木なので BFS は O(N)。
動的グラフなので adjacency list に辺を追加しながら BFS で距離計算。木なので BFS は O(N)。
4クエリ処理
クエリ 2 では find(t) で根を取得し、dia[root][0] を返す。
クエリ 2 では find(t) で根を取得し、dia[root][0] を返す。
5計算量
簡単な実装では O(QN)。実際の競プロでは LCT を使って O(Q log N) にする。
簡単な実装では O(QN)。実際の競プロでは LCT を使って O(Q log N) にする。
計算量
union 1 回: $O(N)$(BFS 4 回)
query 1 回: $O(\alpha(N))$(Union-Find)
全体: $O(QN)$ — LCT を使うと $O(Q \log N)$
メモリ: $O(N + M)$
query 1 回: $O(\alpha(N))$(Union-Find)
全体: $O(QN)$ — LCT を使うと $O(Q \log N)$
メモリ: $O(N + M)$
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 直径候補 4 通りを見落とす | (a1,b2) と (b1,a2) の計算忘れ | 4 ペア全候補を確認 |
| 辺追加前に dist 計算 | 木がつながっていない | 辺を adj に追加してから BFS |
| Union-Find の rank/size を直径 info で上書き | dia と uf を別配列で管理 | dia[ru] と rank_uf[ru] を分離 |
| BFS で連結でない場合 | 異なる成分への距離クエリ | 直径端点は同一成分内を保証 |
次のステップ
- 発展: Link-Cut Tree での O(log N) 動的直径管理
- 応用: 辺削除ありの動的直径(Offline + Undo DSU)
- 類題: 森の直径クエリ(複数成分の最大直径)