Official
E - 配達ドライバーの巡回 / Delivery Driver's Route Editorial by admin
DeepSeek V3概要
この問題は、すべての町を少なくとも1回訪問して出発点に戻る最短経路(巡回セールスマン問題)を求める問題です。
考察
- すべての町を訪問して戻る経路は、同じ町や道路を複数回通ることが許されていますが、直接すべての経路を列挙すると計算量が膨大になります(\(O(N!)\))。
- グラフが完全グラフではないため、すべての町の組の間で直接道路が存在するわけではありません。そのため、まず全点間の最短距離を求める必要があります。
- 制約が \(N \leq 15\) と小さいため、ビットDP(動的計画法)を用いて効率的に解くことができます。ビットDPでは、訪問済みの町の集合と現在位置を状態として持ち、最短移動時間を計算します。
アルゴリズム
- 全点間最短距離の計算: ワーシャル-フロイド法を用いて、すべての町の組の間の最短移動時間を計算します。これにより、直接道路がなくても迂回して移動できる場合に対応します。
- ビットDPの初期化:
dp[s][i]を、訪問済みの町の集合がs(ビットマスク)で現在地が町iであるときの最小移動時間と定義します。- 初期状態は出発点である町0のみ訪問済みなので、
dp[1][0] = 0とします。
- DPの更新:
- 現在の状態
(s, i)から、まだ訪問していない町jに移動する場合を考え、dp[ns][j] = min(dp[ns][j], dp[s][i] + dist[i][j])で更新します(ns = s | (1<<j))。
- 現在の状態
- 答えの計算:
- すべての町を訪問した状態(
s = (1<<n)-1)から、町0に戻る経路を考慮し、min(dp[full][i] + dist[i][0])を求めます。
- すべての町を訪問した状態(
計算量
- 時間計算量: \(O(N^3 + 2^N \cdot N^2)\)
- ワーシャル-フロイド法: \(O(N^3)\)
- ビットDP: 状態数 \(O(2^N \cdot N)\)、各状態からの遷移 \(O(N)\)
- 空間計算量: \(O(2^N \cdot N)\)
実装のポイント
ワーシャル-フロイド法を実行する前に、自己ループ(同じ町への移動)は0に、それ以外は大きな値(
INF)で初期化します。道路の入力では、同じ町の組に複数の道路が存在する可能性があるため、最小の重みのみを採用します。
DPテーブルの初期化では、到達不可能な状態を
INFで表現します。最終的にすべての町を訪問した後、町0に戻れるかどうか(
dist[i][0] != INF)を確認する必要があります。ソースコード
import sys
def main():
data = sys.stdin.read().split()
if not data:
print(-1)
return
n = int(data[0])
m = int(data[1])
INF = 10**18
dist = [[INF] * n for _ in range(n)]
for i in range(n):
dist[i][i] = 0
index = 2
for i in range(m):
u = int(data[index]); v = int(data[index+1]); w = int(data[index+2])
index += 3
u -= 1
v -= 1
if w < dist[u][v]:
dist[u][v] = w
dist[v][u] = w
for k in range(n):
for i in range(n):
if dist[i][k] == INF:
continue
for j in range(n):
if dist[i][j] > dist[i][k] + dist[k][j]:
dist[i][j] = dist[i][k] + dist[k][j]
dp = [[INF] * n for _ in range(1<<n)]
dp[1][0] = 0
for s in range(1<<n):
for i in range(n):
if dp[s][i] == INF:
continue
for j in range(n):
if s & (1<<j):
continue
if dist[i][j] == INF:
continue
ns = s | (1<<j)
if dp[ns][j] > dp[s][i] + dist[i][j]:
dp[ns][j] = dp[s][i] + dist[i][j]
ans = INF
full = (1<<n) - 1
for i in range(n):
if dp[full][i] != INF and dist[i][0] != INF:
total = dp[full][i] + dist[i][0]
if total < ans:
ans = total
if ans == INF:
print(-1)
else:
print(ans)
if __name__ == "__main__":
main()
この解説は deepseekv3 によって生成されました。
posted:
last update: