公式

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

  1. Graph Construction: Store the given road information in adjacency list format.
  2. 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_matrix storing the mutual shortest distances.
  3. Optimization via Bit DP: Define the following DP table: dp[mask][i] := the minimum travel time when the visit state of delivery destinations is mask (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])
  4. 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_matrix to 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 to heapq can 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.

投稿日時:
最終更新: