E - 配送ルートの最適化 / Optimization of Delivery Routes Editorial by admin
Claude 4.6 Opus (Thinking)Overview
This is the so-called Traveling Salesman Problem (TSP): starting from the distribution center (destination \(1\)), visit all \(N\) destinations exactly once, and return to the distribution center, minimizing the fuel cost (the sum of squared Euclidean distances).
Analysis
Essence of the Problem
This problem asks us to find, on a complete graph with \(N\) vertices, the Hamiltonian cycle starting and ending at vertex \(1\) (a cycle that visits every vertex exactly once) with the minimum total edge weight.
Naive Approach and Its Limitations
The simplest method is to enumerate all permutations of the \(N\) destinations. However, the number of permutations is \((N-1)!\), and when \(N = 16\), this gives \((16-1)! = 15! \approx 1.3 \times 10^{12}\), which is far too large to handle in time.
How to Solve It
While enumerating all visit orders causes a state explosion, if we only record “which destinations have been visited (as a set)” and “which destination we are currently at”, the detailed order of intermediate visits becomes unnecessary. This is the idea behind bitmask DP (bit DP).
Since \(N \leq 16\), the visited set can be represented as an \(N\)-bit integer, and the number of states is \(2^N \times N\), which is at most \(2^{16} \times 16 = 1{,}048{,}576\) — small enough.
Algorithm
1. Precomputation of the Cost Matrix
For all destination pairs \((i, j)\), precompute the travel cost \((X_i - X_j)^2 + (Y_i - Y_j)^2\).
2. Bitmask DP Definition
- State: \(dp[S][u]\) = “the minimum cost when the set of visited destinations is \(S\) (bitmask) and the last visited destination is \(u\)”
- Initial state: \(dp[\{0\}][0] = 0\) (we are at destination \(1\), i.e., vertex \(0\) in 0-indexed, with cost \(0\))
- Transition: From a state where we are in set \(S\) at vertex \(u\), move to an unvisited vertex \(v\) (\(v \notin S\)): $\(dp[S \cup \{v\}][v] = \min(dp[S \cup \{v\}][v],\; dp[S][u] + \text{dist}[u][v])\)$
3. Computing the Answer
From the state where all vertices have been visited (\(S = \{0, 1, \ldots, N-1\}\), i.e., bitmask \(2^N - 1\)), add the cost of returning to the distribution center (vertex \(0\)) and take the minimum: $\(\text{ans} = \min_{u=0}^{N-1} \left( dp[2^N - 1][u] + \text{dist}[u][0] \right)\)$
Concrete Example
For \(N = 3\) with vertices \(0, 1, 2\): - \(dp[001][0] = 0\) (only vertex 0 visited, currently at vertex 0) - \(dp[011][1] = \text{dist}[0][1]\) (moved from vertex 0 → 1) - \(dp[111][2] = \text{dist}[0][1] + \text{dist}[1][2]\) (moved from vertex 0 → 1 → 2) - Finally, \(dp[111][2] + \text{dist}[2][0]\) gives the cost of returning to vertex 0
Complexity
- Time complexity: \(O(2^N \times N^2)\) (for each of the \(2^N \times N\) states, we try \(N\) possible transitions)
- When \(N = 16\), this is approximately \(2^{16} \times 16^2 \approx 1.7 \times 10^7\), which is fast enough
- Space complexity: \(O(2^N \times N)\) (size of the DP table)
Implementation Notes
Destinations are given in 1-indexed form, but in the code they are handled as 0-indexed, with the distribution center as vertex \(0\).
States in bitmask \(S\) that do not include vertex \(0\) (the distribution center) are skipped to avoid unnecessary computation (
if not (S & 1): continue).The DP initial values are set to
INF(infinity), naturally excluding unreachable states.By precomputing the cost matrix, distance calculations during the DP are done in \(O(1)\).
Source Code
import sys
def main():
input_data = sys.stdin.read().split()
idx = 0
N = int(input_data[idx]); idx += 1
coords = []
for i in range(N):
x = int(input_data[idx]); idx += 1
y = int(input_data[idx]); idx += 1
coords.append((x, y))
# Precompute distance (cost) matrix
dist = [[0] * N for _ in range(N)]
for i in range(N):
for j in range(N):
dx = coords[i][0] - coords[j][0]
dy = coords[i][1] - coords[j][1]
dist[i][j] = dx * dx + dy * dy
# TSP with bitmask DP
# dp[S][i] = minimum cost to visit exactly the set S of nodes, ending at node i
# Start at node 0 (1-indexed node 1)
INF = float('inf')
full = 1 << N
dp = [[INF] * N for _ in range(full)]
dp[1][0] = 0 # Start at node 0, visited set = {0}
for S in range(1, full):
if not (S & 1):
continue # Must include node 0
for u in range(N):
if dp[S][u] == INF:
continue
if not (S >> u & 1):
continue
for v in range(N):
if S >> v & 1:
continue
nS = S | (1 << v)
cost = dp[S][u] + dist[u][v]
if cost < dp[nS][v]:
dp[nS][v] = cost
# Answer: visit all nodes and return to node 0
full_mask = full - 1
ans = INF
for u in range(N):
if dp[full_mask][u] + dist[u][0] < ans:
ans = dp[full_mask][u] + dist[u][0]
print(ans)
main()
This editorial was generated by claude4.6opus-thinking.
posted:
last update: