Official

D - 配達ルートの最適化 / Optimizing Delivery Routes Editorial by admin

Gemini 3.1 Pro (Thinking)

Overview

This problem asks you to find the shortest path (minimum travel time) from vertex \(1\) to vertex \(N\) in an undirected graph where some edge weights have been modified.

Analysis

This problem can be reduced to a shortest path problem on a weighted undirected graph, where each location is a “vertex,” each road is an “edge,” and the travel time is the “edge weight.”

Since all edge weights are non-negative, Dijkstra’s Algorithm is the optimal choice. If we were to use a naive depth-first search (DFS) that explores all possible path combinations, the computational complexity would grow exponentially, resulting in TLE (Time Limit Exceeded). Even using the Bellman-Ford algorithm would take \(O(NM)\) time, which would also result in TLE under the given constraints (\(N, M \le 2 \times 10^5\)).

The increase in travel time due to speed restrictions can be handled during the graph construction phase. We can store the road numbers subject to speed restrictions in a set (Set) beforehand, and when registering edge information in the adjacency list, simply double the weight if that road is subject to restrictions.

Algorithm

  1. Input and Preprocessing:
    • Store the road numbers \(C_1, C_2, \ldots, C_K\) subject to speed restrictions in a hash set (Set) for fast lookup.
  2. Graph Construction:
    • Create an “adjacency list” to manage the edges connected to each vertex.
    • When adding road \(i\) to the adjacency list, if road number \(i\) is in the set, set the travel time to \(2W_i\); otherwise, set it to \(W_i\). Add edges in both directions.
  3. Running Dijkstra’s Algorithm:
    • Prepare an array dist to manage the shortest distance from vertex \(1\) to each vertex, initialized to infinity (\(\infty\)), with dist[1] set to \(0\).
    • Insert (distance 0, vertex 1) into the priority queue (heap).
    • Extract the vertex \(u\) with the currently known shortest distance from the queue, and for each neighboring vertex \(v\), if distance to vertex u + edge weight is less than the current dist[v], update dist[v] and add (new distance, vertex v) to the queue.
    • Repeat this until the queue is empty or the shortest distance to the target vertex \(N\) is determined.
  4. Output the Answer:
    • If dist[N] remains infinity, the destination is unreachable, so output -1; otherwise, output dist[N].

Complexity

  • Time Complexity: \(O(M \log M)\) or \(O(M \log N)\)
    • Graph construction takes \(O(M)\), and building the speed restriction set takes \(O(K)\).
    • In Dijkstra’s algorithm, insertions and extractions from the priority queue occur at most \(M\) times, resulting in \(O(M \log M)\) time.
    • Overall, the complexity is \(O(M \log M)\), which runs sufficiently fast under the given constraints.
  • Space Complexity: \(O(N + M)\)
    • Memory is needed for the adjacency list proportional to the number of vertices \(N\) and edges \(M\), as well as the distance array and priority queue.

Implementation Tips

  • Setting Infinity (INF): From the constraints, the maximum travel time per edge is \(10^9\), and a path may traverse up to \(N-1\) edges. Therefore, the maximum shortest path length can be approximately \(2 \times 10^5 \times 10^9 = 2 \times 10^{14}\). Thus, the infinity value used to initialize distances must be set to a sufficiently large value such as \(10^{18}\).

  • Fast I/O: Since the amount of input data is large, in Python, reading all input at once using sys.stdin.read().split() can significantly reduce execution time.

  • Skipping Unnecessary Exploration: Within the Dijkstra loop, if the distance d extracted from the queue is greater than the already recorded dist[u], it is outdated (non-optimal) information, so it is important to skip it with continue. This prevents unnecessary computation.

    Source Code

import sys
import heapq

def solve():
    input_data = sys.stdin.read().split()
    if not input_data:
        return
    
    N = int(input_data[0])
    M = int(input_data[1])
    K = int(input_data[2])
    
    idx = 3
    U = [0] * (M + 1)
    V = [0] * (M + 1)
    W = [0] * (M + 1)
    for i in range(1, M + 1):
        U[i] = int(input_data[idx])
        V[i] = int(input_data[idx+1])
        W[i] = int(input_data[idx+2])
        idx += 3
        
    restricted = set()
    if K > 0:
        for _ in range(K):
            restricted.add(int(input_data[idx]))
            idx += 1
            
    adj = [[] for _ in range(N + 1)]
    for i in range(1, M + 1):
        u = U[i]
        v = V[i]
        w = W[i]
        if i in restricted:
            w *= 2
        adj[u].append((v, w))
        adj[v].append((u, w))
        
    INF = 10**18
    dist = [INF] * (N + 1)
    dist[1] = 0
    pq = [(0, 1)]
    
    while pq:
        d, u = heapq.heappop(pq)
        
        if d > dist[u]:
            continue
            
        if u == N:
            break
            
        for v, w in adj[u]:
            if dist[v] > d + w:
                dist[v] = d + w
                heapq.heappush(pq, (dist[v], v))
                
    if dist[N] == INF:
        print(-1)
    else:
        print(dist[N])

if __name__ == '__main__':
    solve()

This editorial was generated by gemini-3.1-pro-thinking.

posted:
last update: