公式

D - チェックポイントラリー / Checkpoint Rally 解説 by admin

Gemini 3.0 Flash

Overview

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:

  1. Shortest distance from the start (vertex \(1\)) to \(P_1\)
  2. Shortest distance from \(P_1\) to \(P_2\)
  3. Shortest distance from \(P_{K-1}\) to \(P_K\)
  4. 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:

  1. Graph construction: Store the given \(M\) roads in adjacency list format.
  2. Organizing the visit route: Compile the visiting order into a list [1, P_1, P_2, ..., P_K, N].
  3. 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.
  4. 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 -1 and terminate at that point.
  5. 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 remains INF after 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.

投稿日時:
最終更新: