E - 配達ドライバーの巡回 / Delivery Driver's Route Editorial by admin
Gemini 3.0 Flash (Thinking)概要
この問題は、町1から出発して全ての町を少なくとも1回ずつ訪問し、再び町1に戻ってくるための最短時間を求める問題です。いわゆる「巡回セールスマン問題(TSP)」の変種ですが、同じ町や道路を何度も通ってよいという点が特徴です。
考察
1. 「少なくとも1回」を「ちょうど1回」に変換する
通常の巡回セールスマン問題は「各都市をちょうど1回ずつ通る」という制約がありますが、この問題では同じ町を複数回通ることが許されています。しかし、あらかじめ全町ペア間の最短距離を求めておけば、「町 A から町 B へ最短経路で移動する」という操作を「1回の移動」とみなすことで、通常の巡回セールスマン問題として扱うことができます。
例えば、町1から町3へ行く途中で町2を通ったとしても、それは「町1→町2→町3」という移動を1つにまとめたものと考えれば、実質的に全ての町を「最短で巡る」問題に集約されます。
2. 制約に注目
町の数 \(N\) が最大で \(15\) と非常に小さいことに注目します。 全順列を試すと \(15! \approx 1.3 \times 10^{12}\) となり間に合いませんが、ビットDP(動的計画法)を用いることで \(O(N^2 2^N)\) の計算量で解くことができます。\(15^2 \times 2^{15} \approx 7.3 \times 10^6\) であり、制限時間内に十分収まります。
アルゴリズム
ステップ1:全点対間最短経路の計算(ワーシャル・フロイド法)
まず、与えられた道路情報から、任意の2つの町 \((i, j)\) 間の最短移動時間を求めます。 これはワーシャル・フロイド法を用いることで \(O(N^3)\) で計算可能です。もし、ある町から別の町へ行く経路が存在しない場合、その距離は無限大(INF)として扱います。
ステップ2:ビットDPによる巡回パスの計算
次に、以下のDPテーブルを定義します。
dp[mask][v] : 現在までに訪れた町の集合が mask で、今いる町が v であるときの移動時間の最小値
maskは、各町を訪問済みなら1、未訪問なら0とするビット表現(整数)です。- 初期状態:
dp[1][0] = 0(町1(インデックス0)にいて、訪問済みは町1のみ) - 遷移:今いる町
uから次に訪れる町vを選び、dp[mask | (1 << v)][v]を更新します。
ステップ3:町1への帰還と最終回答
全ての町を訪問した状態 dp[(1 << N) - 1][u](全てのビットが1の状態)まで計算したら、最後に今いる町 u から出発地点の町1に戻る時間を加算します。
その中での最小値が答えとなります。どこかの町へ到達できない場合や、全ての町を巡って戻ってこれない場合は -1 を出力します。
計算量
- 時間計算量: \(O(N^3 + N^2 2^N)\)
- ワーシャル・フロイド法に \(O(N^3)\)
- ビットDPの遷移に \(O(N^2 2^N)\) かかります。
- 空間計算量: \(O(N^2 + N 2^N)\)
- 最短距離行列に \(O(N^2)\)
- DPテーブルに \(O(N 2^N)\) 使用します。
実装のポイント
インデックスの調整: 問題文では町が \(1 \sim N\) ですが、コード上では \(0 \sim N-1\) として扱うとビット演算がしやすくなります。
無限大の初期化: 距離やDPテーブルの初期値は、十分に大きな値(
10^15など)で初期化してください。到達可能性の判定: 全ての町を回る前に、そもそも町1から到達できない町がある場合は、即座に
-1を出力する処理を入れると効率的です。ソースコード
import sys
# The problem asks for the minimum travel time to visit all N towns at least once,
# starting from town 1 and returning to town 1.
# This is a variation of the Traveling Salesperson Problem (TSP).
# Since we can visit towns and roads multiple times, we can simplify the problem
# by first calculating the shortest paths between all pairs of towns.
# Then, we solve the standard TSP on the resulting complete graph.
def solve():
# Read all input from standard input and split into a list of strings
try:
input_data = sys.stdin.read().split()
except EOFError:
return
if not input_data:
return
# N is the number of towns, M is the number of roads
N = int(input_data[0])
M = int(input_data[1])
# Use a large integer for infinity. The maximum possible travel time is
# well within this limit (max N * max W = 15 * 10^6).
INF = 10**18
# dist[i][j] will store the shortest path distance between town i and town j.
# Initialize with INF, and 0 for the distance from a town to itself.
dist = [[INF] * N for _ in range(N)]
for i in range(N):
dist[i][i] = 0
ptr = 2
for _ in range(M):
if ptr + 2 >= len(input_data):
break
u = int(input_data[ptr]) - 1
v = int(input_data[ptr+1]) - 1
w = int(input_data[ptr+2])
# If multiple roads exist between the same two towns, keep only the shortest one.
if w < dist[u][v]:
dist[u][v] = w
dist[v][u] = w
ptr += 3
# Floyd-Warshall algorithm to compute all-pairs shortest paths.
# This allows us to treat the problem as a standard TSP on a complete graph.
for k in range(N):
dk = dist[k]
for i in range(N):
di = dist[i]
dik = di[k]
if dik >= INF:
continue
for j in range(N):
new_dist = dik + dk[j]
if new_dist < di[j]:
di[j] = new_dist
# If any town is not reachable from town 1 (index 0), it is impossible to visit all towns.
for i in range(N):
if dist[0][i] >= INF:
print("-1")
return
# DP for TSP:
# dp[mask][u] = minimum distance to have visited the set of towns represented by 'mask',
# with the current location being town 'u'.
# 'mask' is a bitmask where the i-th bit is 1 if town i+1 has been visited.
num_states = 1 << N
dp = [[INF] * N for _ in range(num_states)]
# Starting point: Town 1 (index 0), distance is 0.
dp[1][0] = 0
# Iterate through all bitmasks representing subsets of visited towns.
for mask in range(1, num_states):
dp_mask = dp[mask]
# Optimization: Pre-calculate which towns are not yet in the current mask.
not_in_mask = [v for v in range(N) if not (mask & (1 << v))]
# For each town 'u' that could be the current location:
for u in range(N):
curr_dist = dp_mask[u]
if curr_dist >= INF:
continue
# Try moving to each town 'v' that has not been visited yet.
dist_u = dist[u]
for v in not_in_mask:
new_mask = mask | (1 << v)
new_dist = curr_dist + dist_u[v]
if dp[new_mask][v] > new_dist:
dp[new_mask][v] = new_dist
# The final answer is the minimum distance to visit all towns (full_mask)
# and then return to the starting town (town 1, index 0).
ans = INF
full_mask = num_states - 1
dp_full = dp[full_mask]
dist_0 = dist[0]
for u in range(N):
if dp_full[u] < INF:
# Add the shortest path distance from the last town u back to town 1.
ans = min(ans, dp_full[u] + dist_0[u])
# If ans is still INF, it means it was impossible to visit all towns and return.
if ans >= INF:
print("-1")
else:
print(ans)
if __name__ == '__main__':
solve()
この解説は gemini-3-flash-thinking によって生成されました。
posted:
last update: