問題
$N$ 頂点 $M$ 辺の重み付き有向グラフが与えられる。頂点 $1$ からすべての頂点への最短距離を求めよ。ただし以下の 追加クエリ が $Q$ 個オンラインで与えられる:
decrease u w: 頂点 $u$ への暫定距離を $w$ に強制更新する(必ず現在値より小さい値が来る)。クエリ直後の全頂点への最短距離を再計算して答えよ。
制約
| パラメータ | 範囲 |
|---|---|
| $N$ | $2 \le N \le 10^5$ |
| $M$ | $1 \le M \le 3 \times 10^5$ |
| $Q$ | $0 \le Q \le 10^4$ |
| $w_i$ | $1 \le w_i \le 10^9$ |
| グラフ | 非負重み |
入出力例
入力例 1
4 5 2
1 2 3
1 3 6
2 3 1
2 4 5
3 4 2
decrease 4 7
decrease 3 2
出力例 1
Initial: 0 3 4 6
After decrease 4 7: 0 3 4 6
After decrease 3 2: 0 3 2 4
decrease クエリで dist[3]=2 になると、3→4 の辺(コスト2)より dist[4]=4 に更新される。
概念図: Fibonacci Heap vs Binary Heap
ヒント(段階的開示)
ヒント1: 方向性
Fibonacci Heap の decrease-key は $O(1)$ amortized。Python では直接実装が煩雑なため、lazy deletion による
heapq シミュレーションを使う。decrease クエリ後は dist[u] を更新し新しいエントリを push、pop 時に古いエントリをスキップする。
ヒント2: アプローチ
- decrease クエリ:
dist[u] = w; heappush(heap, (w, u)) - pop 時:
if d > dist[u]: continue(lazy deletion) - decrease 後に
relax()を呼ぶと、更新された dist から再 propagation される
ヒント3: コード骨格
def relax():
while heap:
d, u = heapq.heappop(heap)
if d > dist[u]: continue # lazy deletion
for v, w in graph[u]:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
heapq.heappush(heap, (dist[v], v))
# decrease クエリ処理
dist[u] = w
heapq.heappush(heap, (w, u))
relax()
模範解答 (Python)
import heapq
import sys
input = sys.stdin.readline
def solve():
N, M, Q = map(int, input().split())
graph = [[] for _ in range(N + 1)]
for _ in range(M):
u, v, w = map(int, input().split())
graph[u].append((v, w))
INF = float('inf')
dist = [INF] * (N + 1)
dist[1] = 0
heap = [(0, 1)]
def relax():
while heap:
d, u = heapq.heappop(heap)
if d > dist[u]:
continue
for v, w in graph[u]:
nd = dist[u] + w
if nd < dist[v]:
dist[v] = nd
heapq.heappush(heap, (nd, v))
relax()
res = [dist[i] if dist[i] < INF else -1 for i in range(1, N + 1)]
print("Initial:", *res)
for _ in range(Q):
line = input().split()
u, w = int(line[1]), int(line[2])
if w < dist[u]:
dist[u] = w
heapq.heappush(heap, (w, u))
relax()
res = [dist[i] if dist[i] < INF else -1 for i in range(1, N + 1)]
print(f"After decrease {u} {w}:", *res)
solve()
Step-by-Step 解説
Step 1: Fibonacci Heap の概念
Fibonacci Heap は amortized 計算量が優れた優先度付きキュー。decrease-key が $O(1)$ amortized のため、Dijkstra 全体が $O(M + N\log N)$ になる。Python では直接実装が煩雑なため、lazy deletion による heapq シミュレーションを使う。
Step 2: Lazy Deletion の仕組み
- ヒープに古いエントリが残っていても
dist[u]と比較して無視する - decrease クエリ後は
dist[u]を更新し、新しいエントリを push する - pop 時に
d > dist[u]なら skip(これが lazy deletion)
Step 3: オンライン decrease クエリ
decrease クエリは「外部からの距離強制更新」。更新後、その頂点を始点として Dijkstra の relaxation を再開する。visited フラグは使わず dist 値のみで判断する。
Step 4: 計算量分析
| 処理 | 計算量 |
|---|---|
| 初期 Dijkstra | $O(M \log N)$ |
| 各 decrease クエリ | 最悪 $O(M \log N)$ |
| 合計 | $O((Q+1) \cdot M \log N)$ |
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| decrease後にvisitedをリセットしない | 確定済み扱いで再探索されない | dist値のみで判断し visitedは使わない |
d > dist[u] チェック忘れ |
古いエントリを処理してしまう | pop直後に必ずチェック |
| decrease値が現在値より大きい | 最短距離が増加する矛盾 | if w < dist[u] の条件確認 |
次のステップ
発展問題: Radix Heap を実装して整数重み Dijkstra を $O(M + N\sqrt{W})$ に高速化せよ($W$ は最大辺重み)。