E - 配送ルートの最適化 / Optimization of Delivery Routes Editorial by admin
Gemini 3.0 Flash (Thinking)Overview
This problem asks us to minimize the total fuel cost (sum of squared distances) when visiting all \(N\) points on a 2D plane exactly once and returning to the starting point. This is one of the famous problems in graph theory known as the “Traveling Salesman Problem (TSP).”
Analysis
Naive Approach
Consider exhaustively searching all possible visiting orders of the points. Starting from point 1 (the delivery center), there are \((N-1)!\) ways to arrange the remaining \(N-1\) points. When \(N=16\), \((16-1)! = 15! \approx 1.3 \times 10^{12}\), making it impossible to finish computation within the time limit.
Clues Toward an Efficient Solution
When determining a tour route, only the following two pieces of information matter at any given moment: 1. Which points have already been visited 2. Which point we are currently at
When deciding which point to visit next, the detailed path — specifically “in what order the previously visited points were traversed” — does not affect the minimum cost from that point onward. By exploiting this property, we apply Dynamic Programming (DP).
By representing the set of visited points as a bit string (bitmask), we can efficiently manage states.
Algorithm
Bitmask DP
We define the following state:
dp[mask][i]: The minimum cost when all points included in the setmaskhave been visited and we are currently at pointi.
Here, mask is an integer where the bits corresponding to the indices of visited points among the \(N\) points are set to 1 (e.g., if points 0 and 2 have been visited, mask is \(2^0 + 2^2 = 5\)).
1. Initialization
Since we start from point 1 (index 0), we set the state where only point 0 has been visited to cost 0.
- dp[1][0] = 0
- All other dp values are initialized to infinity (\(\infty\))
2. Transitions
From the current state dp[mask][i], when moving to an unvisited point j:
- dp[mask | (1 << j)][j] = min(dp[mask | (1 << j)][j], dp[mask][i] + dist(i, j))
※ dist(i, j) is the fuel cost between points i and j: \((X_i - X_j)^2 + (Y_i - Y_j)^2\).
3. Final Result
The state where all points have been visited is mask = (1 << N) - 1. Since we need to return to point 1 (index 0) at the end, the answer is the following minimum value:
- \(\min_{i=1}^{N-1} (dp[(1 \ll N) - 1][i] + dist(i, 0))\)
Complexity
- Time Complexity: \(O(2^N \cdot N^2)\)
- There are \(2^N \cdot N\) states, and the transition from each state (searching for the next point
j) takes \(O(N)\). - When \(N=16\), \(2^{16} \cdot 16^2 \approx 1.6 \times 10^7\), which is a sufficiently fast computation even for Python.
- There are \(2^N \cdot N\) states, and the transition from each state (searching for the next point
- Space Complexity: \(O(2^N \cdot N)\)
- Depends on the size of the DP table.
Implementation Notes
Precomputation of Distances: To avoid redundantly computing the same values inside loops when calculating costs between two points, it is efficient to precompute and store them in a 2D array
dist[i][j].Bit Operations: Smoothly perform operations such as checking whether point
ihas been visited withmask & (1 << i), or marking it as visited withmask | (1 << i).Loop Order: By iterating over
maskvalues in increasing order, we ensure that the transition source values have always been computed already. In the code here, by usingrange(3, 1 << N, 2), we efficiently explore only oddmaskvalues that necessarily include point 0 (the 1st bit).Source Code
import sys
def solve():
# Read all input at once for speed
input_data = sys.stdin.read().split()
if not input_data:
return
# Parse N
N = int(input_data[0])
# Parse coordinates
coords = []
for i in range(N):
coords.append((int(input_data[1 + 2*i]), int(input_data[2 + 2*i])))
# Precompute squared Euclidean distances between all pairs of points
dist = [[0] * N for _ in range(N)]
for i in range(N):
xi, yi = coords[i]
for j in range(i + 1, N):
xj, yj = coords[j]
d2 = (xi - xj)**2 + (yi - yj)**2
dist[i][j] = dist[j][i] = d2
# DP table: dp[mask][i] is the minimum cost to visit points in 'mask' ending at point index 'i'.
# Point 1 is at index 0.
# N = 20 means 2^20 * 20 elements. 10**15 is used as infinity.
inf = 10**15
dp = [[inf] * N for _ in range(1 << N)]
# Base case: start at point index 0, having visited only that point.
dp[1][0] = 0
# Iterate through all possible masks that include the starting point (bit 0 is set).
# We start from mask 3 (binary 00...011) because mask 1 is the base case.
for mask in range(3, 1 << N, 2):
# Pre-extract indices of set bits in the current mask to speed up inner loops.
set_bits = [i for i in range(N) if (mask & (1 << i))]
# Try each point i in the current mask as the last visited point.
for i in set_bits:
if i == 0:
continue
# prev_mask represents the set of points visited before point i.
prev_mask = mask ^ (1 << i)
dp_prev = dp[prev_mask]
dist_i = dist[i]
# Find the minimum cost to reach point i from any possible previous point j.
m = inf
for j in set_bits:
# dp_prev[j] is the cost to visit points in prev_mask ending at j.
# If j is not in prev_mask or cannot be an end-point, dp_prev[j] is inf.
d = dp_prev[j] + dist_i[j]
if d < m:
m = d
dp[mask][i] = m
# The final answer is the minimum cost to visit all N points and return to point index 0.
full_mask = (1 << N) - 1
ans = inf
dp_full = dp[full_mask]
dist_0 = dist[0]
for i in range(1, N):
res = dp_full[i] + dist_0[i]
if res < ans:
ans = res
# Output the result
print(ans)
if __name__ == '__main__':
solve()
This editorial was generated by gemini-3-flash-thinking.
posted:
last update: