E - 宅配ドライバーの巡回 / Delivery Driver's Route 解説 by admin
Gemini 3.0 Flash (Thinking)Overview
This problem asks for the shortest route that visits all specific locations on a graph (office \(S\) and \(K\) delivery destinations \(D_j\)) and returns to the starting point. It is a variation of the so-called “Traveling Salesman Problem (TSP),” but since movement between locations must follow shortest paths, we solve it by combining shortest distance precomputation with dynamic programming (Bit DP).
Analysis
1. Reduction to the Traveling Salesman Problem
The challenging aspect of this problem is determining the optimal order to visit the delivery destinations. Note that the number of delivery destinations \(K\) is at most 15, which is very small. If we know the “shortest travel time” between the office and each delivery destination, this problem can be solved as a classic Traveling Salesman Problem.
2. Finding Shortest Distances Between Locations
First, we group the following locations as “important locations”: - Office \(S\) - Delivery destinations \(D_1, D_2, \dots, D_K\)
There are a total of \(K+1\) such locations (at most 16). We need to find the shortest distance between any pair of these locations on the graph, but with constraints \(N=10,000, M=20,000\), the Floyd-Warshall algorithm (\(O(N^3)\)) for all-pairs shortest distances is not feasible. Instead, by running Dijkstra’s algorithm from each “important location” as the source, a total of \(K+1\) times, we can efficiently compute the distances between the necessary locations.
3. State Management (Bit DP)
We manage whether each of the \(K\) delivery destinations has been visited using a set. - \(S = \{ \text{set of visited delivery destinations} \}\) - \(v = \text{current delivery destination}\) We use dynamic programming (Bit DP) with these two as the state.
Algorithm
- Graph Construction: Store the given road information in adjacency list format.
- Shortest Distance Matrix Construction:
Run Dijkstra’s algorithm from each of the \(K+1\) locations (office \(S\) and each delivery destination \(D_j\)), and create a \((K+1) \times (K+1)\) matrix
dist_matrixstoring the mutual shortest distances. - Optimization via Bit DP:
Define the following DP table:
dp[mask][i]:= the minimum travel time when the visit state of delivery destinations ismask(a bit set) and the last destination reached is \(D_i\).- Initialization: Set the travel time from office \(S\) to each delivery destination \(D_i\) as
dp[1 << i][i]. - Transition: From the current state
(mask, i), when moving to an unvisited delivery destination \(D_j\):dp[mask | (1 << j)][j] = min(dp[mask | (1 << j)][j], dp[mask][i] + dist_matrix[i+1][j+1])
- Initialization: Set the travel time from office \(S\) to each delivery destination \(D_i\) as
- Final Answer Computation:
Add the return time from the last delivery destination \(D_i\) back to office \(S\) to the state
dp[(1 << K) - 1][i]where all delivery destinations have been visited, and output the minimum value.
Complexity
- Dijkstra’s Algorithm: \(O(K \cdot M \log N)\)
- Dijkstra’s algorithm at \(O(M \log N)\) per run is executed \(K+1\) times.
- Bit DP: \(O(K^2 \cdot 2^K)\)
- The number of states is \(K \cdot 2^K\), and there are \(K\) transitions from each state.
When \(K=15\), \(K^2 \cdot 2^K \approx 225 \times 32,768 \approx 7.3 \times 10^6\), which comfortably fits within the time limit.
- Total Time Complexity: \(O(K \cdot M \log N + K^2 \cdot 2^K)\)
- Space Complexity: \(O(N + M + K \cdot 2^K)\)
Implementation Notes
Speeding Up Dijkstra’s Algorithm: In each Dijkstra execution, you can reduce runtime by terminating the search early once the shortest distances to all “important locations” have been determined.
Bit DP Indexing: If you assign index 0 of
dist_matrixto office \(S\) and indices 1 onward to delivery destinations \(D_1 \dots D_K\), the DP transitions become easier to write.Optimization in Python: Using bulk input with
sys.stdin.read().split()and direct references toheapqcan help achieve faster execution even in Python.Source Code
import sys
import heapq
def solve():
# Read all input at once for faster processing
input_data = sys.stdin.read().split()
if not input_data:
return
ptr = 0
N = int(input_data[ptr])
ptr += 1
M = int(input_data[ptr])
ptr += 1
# Adjacency list representation of the graph
adj = [[] for _ in range(N + 1)]
for _ in range(M):
u = int(input_data[ptr])
ptr += 1
v = int(input_data[ptr])
ptr += 1
w = int(input_data[ptr])
ptr += 1
adj[u].append((v, w))
adj[v].append((u, w))
# Starting location and delivery locations
S = int(input_data[ptr])
ptr += 1
K = int(input_data[ptr])
ptr += 1
D = [int(input_data[ptr + i]) for i in range(K)]
# All points of interest (starting point + delivery points)
nodes = [S] + D
num_nodes = K + 1
# Shortest path distances between all pairs of nodes in 'nodes'
dist_matrix = [[0] * num_nodes for _ in range(num_nodes)]
# Map intersection ID to its index in the 'nodes' list for early exit in Dijkstra
is_node = [-1] * (N + 1)
for idx, node in enumerate(nodes):
is_node[node] = idx
heappop = heapq.heappop
heappush = heapq.heappush
INF = 10**18
# Run Dijkstra's algorithm from each point of interest
for i in range(num_nodes):
start_node = nodes[i]
distances = [INF] * (N + 1)
distances[start_node] = 0
pq = [(0, start_node)]
found_count = 0
while pq:
d, u = heappop(pq)
if d > distances[u]:
continue
# Count how many target points of interest we've found
if is_node[u] != -1:
found_count += 1
# If all targets are reached from this source, we can stop the search
if found_count == num_nodes:
break
for v, w in adj[u]:
new_dist = d + w
if distances[v] > new_dist:
distances[v] = new_dist
heappush(pq, (new_dist, v))
# Store distances to other points of interest in the matrix
for j in range(num_nodes):
dist_matrix[i][j] = distances[nodes[j]]
# Traveling Salesperson Problem (TSP) using Dynamic Programming
# dp[mask][i] is the minimum distance to visit delivery nodes in 'mask', ending at delivery node D[i]
dp = [[INF] * K for _ in range(1 << K)]
# Initial state: travel from starting point S to each delivery node
for i in range(K):
# dist_matrix[0][i+1] is distance from nodes[0] (S) to nodes[i+1] (D[i])
dp[1 << i][i] = dist_matrix[0][i+1]
# Transition: visit more delivery nodes
for mask in range(1, 1 << K):
dp_mask = dp[mask]
for i, d_i in enumerate(dp_mask):
if d_i >= INF:
continue
# Current distance to reach D[i] after visiting nodes in 'mask'
dist_matrix_i_plus_1 = dist_matrix[i+1]
for j in range(K):
# If delivery node D[j] has not been visited yet
if not (mask & (1 << j)):
next_mask = mask | (1 << j)
new_dist = d_i + dist_matrix_i_plus_1[j+1]
if dp[next_mask][j] > new_dist:
dp[next_mask][j] = new_dist
# Final step: return to starting point S after visiting all delivery nodes
ans = INF
full_mask = (1 << K) - 1
final_dp = dp[full_mask]
for i in range(K):
# dist_matrix[i+1][0] is the distance from D[i] to S
res = final_dp[i] + dist_matrix[i+1][0]
if res < ans:
ans = res
# Output the minimum total travel time
print(ans)
if __name__ == '__main__':
solve()
This editorial was generated by gemini-3-flash-thinking.
投稿日時:
最終更新: