公式
E - 配達ドライバーの巡回 / Delivery Driver's Route 解説 by admin
Qwen3-Coder-480B概要
この問題は、すべての町を少なくとも1回訪問し、最後に初期地点に戻る最短経路を求める「巡回セールスマン問題(TSP)」の変形です。
考察
この問題では、町の数 \(N\) が最大で \(15\) と小さいため、全探索的なアプローチが有効です。しかし単純にすべての順列を試すと、\(N!\) 通りとなり計算量が爆発します(例:\(15! \approx 1.3 \times 10^{12}\) でTLE)。
また、同じ町や道を複数回通って良いという条件から、直接辺でつながっていなくても最短経路で移動可能です。そのため、最初にすべての町間の最短距離を前もって求めると、その後の経路探索が楽になります。
このように、「まず最短距離を前処理しておき」「その後で効率的に経路を探索する」という2段階のアプローチが有効です。具体的には以下のように進めます: 1. Floyd-Warshall法で全点対間の最短距離を求める。 2. bitDP(ビット動的計画法)で訪問済みの町の集合と現在地を状態として、最小コストを更新していく。
さらに、すべての町に到達できない場合は -1 を出力する必要があるので、最初に連結性のチェックを行う必要があります。
アルゴリズム
ステップ1: 全点対最短距離の前処理
- グラフを隣接行列で表現し、Floyd-Warshall法により任意の2頂点間の最短距離を求めます。
- 時間計算量は \(O(N^3)\)。
ステップ2: bitDPによる巡回経路探索
- bitDPを用いて、訪問済みの町の集合をビットで管理します。
dp[S][i]:= 町の集合 \(S\) を訪問し、現在町 \(i\) にいるときの最小コスト。- 状態数は \(2^{N-1} \times N\) (ただし、最初の町0は常に含むので残り \(N-1\) の部分集合)。
- 遷移は、現在の状態から未訪問の町に移動する場合に行われます。
- 最後に、すべての町を訪問した状態から町0に戻るコストを加えて全体の最小値を求めます。
計算量
- 時間計算量: \(O(N^3 + N^2 \cdot 2^N)\)
- Floyd-Warshall: \(O(N^3)\)
- bitDPの遷移: 各状態 \(S\) に対して、現在地 \(i\) と次の町 \(j\) を走査 → \(O(N \cdot 2^N \cdot N) = O(N^2 \cdot 2^N)\)
- 空間計算量: \(O(N^2 + N \cdot 2^N)\)
- 隣接行列とDPテーブルによる。
実装のポイント
- 町の番号は1-indexedだが、内部的には0-indexedに変換して扱う(コード中では
u-1,v-1)。 - bitDPの際に、最初の町(町0)は常に訪問済みとして扱い、集合 \(S\) には町1〜N-1だけを含める。
- Floyd-Warshall後、町0から他のすべての町に到達可能かチェックし、到達不能があれば
-1を出力。 - DP初期化:
dp[0][0] = 0(集合空、初期位置)。 - 最終ステップで、すべての町を訪問した状態から町0に戻るコストを忘れずに足す。
## ソースコード
```python
import sys
from itertools import permutations
def main():
import sys
input = sys.stdin.read
data = input().split()
N = int(data[0])
M = int(data[1])
edges = []
index = 2
for _ in range(M):
u = int(data[index])
v = int(data[index+1])
w = int(data[index+2])
edges.append((u-1, v-1, w))
index += 3
# グラフ構築 (隣接行列)
INF = float('inf')
dist = [[INF]*N for _ in range(N)]
for i in range(N):
dist[i][i] = 0
for u, v, w in edges:
dist[u][v] = min(dist[u][v], w)
dist[v][u] = min(dist[v][u], w)
# Floyd-Warshall
for k in range(N):
for i in range(N):
for j in range(N):
if dist[i][k] + dist[k][j] < dist[i][j]:
dist[i][j] = dist[i][k] + dist[k][j]
# ノード1(0-indexed)から他のノードへの最短距離をチェック
# ただし、すべてのノードに到達可能か確認する必要がある
reachable = True
for i in range(N):
if dist[0][i] == INF:
reachable = False
break
if not reachable:
print(-1)
return
# ノード集合 {1, ..., N-1} の部分集合に対するbitDP
# dp[S][i] := 訪問済みノード集合S、現在位置i における最小コスト
ALL = 1 << (N-1)
dp = [[INF]*N for _ in range(ALL)]
dp[0][0] = 0 # 最初は町0(1-indexedで1)にいる
for S in range(ALL):
for i in range(N):
if dp[S][i] == INF:
continue
# 次に訪問するノードj (1-indexed -> 0-indexed: 1 to N-1 => 1 to N-1)
for j in range(1, N):
if (S >> (j-1)) & 1:
continue
new_S = S | (1 << (j-1))
cost = dist[i][j]
if dp[new_S][j] > dp[S][i] + cost:
dp[new_S][j] = dp[S][i] + cost
# 最後に町0に戻る
result = INF
full_set = (1 << (N-1)) - 1
for i in range(N):
if dp[full_set][i] != INF:
result = min(result, dp[full_set][i] + dist[i][0])
if result == INF:
print(-1)
else:
print(result)
if __name__ == "__main__":
main()
この解説は qwen3-coder-480b によって生成されました。
投稿日時:
最終更新: