D - チェックポイントラリー / Checkpoint Rally 解説 by admin
Gemini 3.0 FlashOverview
This is a problem of finding the minimum total travel time when moving from start to goal on a graph, passing through multiple checkpoints in a specified order.
Analysis
The most important aspect of this problem is that “the order of visiting checkpoints is predetermined as \(P_1, P_2, \ldots, P_K\)”.
If the visiting order were free, this would become the notoriously difficult “Traveling Salesman Problem.” However, since the order is fixed, the problem can be decomposed into finding the shortest distance for each of the following segments:
- Shortest distance from the start (vertex \(1\)) to \(P_1\)
- Shortest distance from \(P_1\) to \(P_2\)
- …
- Shortest distance from \(P_{K-1}\) to \(P_K\)
- Shortest distance from \(P_K\) to the goal (vertex \(N\))
The overall minimum travel time is the sum of the shortest distances for all these segments.
To find the shortest distance for each segment, Dijkstra’s algorithm is suitable since there are no negative edges. Checking the constraints, the number of checkpoints \(K\) is at most \(10\), which is very small, so running Dijkstra’s algorithm at most \(K+1\) times (once from each starting point) will comfortably fit within the time limit.
Algorithm
The solution is computed using the following steps:
- Graph construction: Store the given \(M\) roads in adjacency list format.
- Organizing the visit route: Compile the visiting order into a list
[1, P_1, P_2, ..., P_K, N]. - Shortest distance computation:
- Run Dijkstra’s algorithm from each vertex in the list (excluding the endpoint \(N\)) as the source.
- To handle cases where the same vertex appears multiple times, it is efficient to memoize (cache) the computation results.
- Calculating the total time:
- Sum up the shortest distances for each segment \((path_i, path_{i+1})\).
- If there is an unreachable vertex (distance is infinity) along the way, output
-1and terminate at that point.
- Output the result: Output the sum of all segments as the minimum travel time.
Complexity
- Time complexity: \(O(K \cdot M \log N)\)
- The complexity of a single run of Dijkstra’s algorithm is \(O(M \log N)\).
- Since this is repeated at most \(K+1\) times, the overall complexity is \(O(K \cdot M \log N)\).
- With \(K \le 10, M = 10^5, N = 5 \times 10^4\), this amounts to roughly \(1.7 \times 10^7\) operations, which fits within the time limit.
- Space complexity: \(O(N + M)\)
- \(O(N + M)\) for storing the adjacency list, and \(O(N)\) for Dijkstra’s distance array.
Implementation Notes
Generalizing Dijkstra’s algorithm: Implement it as a function that takes the source vertex as an argument to avoid code duplication.
Unreachability detection: Set initial values to a very large value (
INF), and if the value remainsINFafter running Dijkstra’s algorithm, determine that the vertex is unreachable.Fast I/O: Since \(N\) and \(M\) can be large, in Python, reading all input at once using
sys.stdin.read().split()can reduce execution time.Source Code
import sys
import heapq
def solve():
# Read all input at once for faster processing
try:
raw_data = sys.stdin.read()
if not raw_data:
return
input_data = raw_data.split()
except EOFError:
return
if not input_data:
return
# Parse N (checkpoints), M (roads), and K (checkpoints to visit)
N = int(input_data[0])
M = int(input_data[1])
K = int(input_data[2])
# Build the graph as an adjacency list
# adj[u] stores a list of (neighbor, travel_time) tuples
adj = [[] for _ in range(N + 1)]
ptr = 3
for _ in range(M):
u = int(input_data[ptr])
v = int(input_data[ptr+1])
t = int(input_data[ptr+2])
adj[u].append((v, t))
adj[v].append((u, t))
ptr += 3
# The K checkpoints to be visited in the given order
P = list(map(int, input_data[ptr:ptr+K]))
# The full sequence of nodes to visit: Start at 1 -> P1 -> P2 -> ... -> PK -> End at N
path = [1] + P + [N]
# We only need to run Dijkstra's algorithm from each unique starting point in the path.
# The last node N is only a destination (unless it is also one of the specified checkpoints).
unique_starts = set(path[:-1])
# Cache to store shortest path distances from each unique starting node
memo = {}
INF = 10**18 # A sufficiently large value for infinity
# Localize heapq functions for faster lookup in the Dijkstra loop
push = heapq.heappush
pop = heapq.heappop
# Implementation of Dijkstra's algorithm
def run_dijkstra(start_node):
distances = [INF] * (N + 1)
distances[start_node] = 0
pq = [(0, start_node)]
while pq:
d, u = pop(pq)
# If a shorter path to node u has already been found, skip this entry
if d > distances[u]:
continue
# Relax all edges connected to node u
for v, weight in adj[u]:
new_dist = d + weight
if new_dist < distances[v]:
distances[v] = new_dist
push(pq, (new_dist, v))
return distances
# Pre-calculate Dijkstra results for all required starting nodes
for u in unique_starts:
memo[u] = run_dijkstra(u)
total_time = 0
# Sum the shortest distances for each consecutive segment of the required path
for i in range(len(path) - 1):
u = path[i]
v = path[i+1]
# Retrieve the shortest distance from u to v from the memoized results
d_uv = memo[u][v]
# If any part of the journey is impossible, output -1
if d_uv >= INF:
print("-1")
return
total_time += d_uv
# Output the minimum total travel time required to complete the rally
print(total_time)
if __name__ == "__main__":
solve()
This editorial was generated by gemini-3-flash-preview.
投稿日時:
最終更新: