公式

E - 宅配ドライバーの巡回 / Delivery Driver's Route 解説 by admin

Claude 4.6 Opus (Thinking)

Overview

This problem asks for the shortest travel time when starting from the office, visiting all \(K\) delivery destinations, and returning to the office. Given the constraint \(K \leq 15\), we solve the Traveling Salesman Problem (TSP) using bitmask DP.

Analysis

Problems with the Naive Approach

There are \(K!\) possible orderings for visiting \(K\) delivery destinations. When \(K = 15\), \(15! \approx 1.3 \times 10^{12}\), so trying all permutations is far too slow.

Key Observations

  1. Shortest paths on the graph can be precomputed: When considering the tour order, we don’t need to traverse intersections one by one. As long as we know the shortest distances between the office \(S\) and each delivery destination \(D_j\), the problem reduces to “TSP over \((K+1)\) vertices.”

  2. \(K \leq 15\) is a signal for bitmask DP: Although the Traveling Salesman Problem is NP-hard, when the number of vertices is small (roughly \(20\) or fewer), it can be solved with bitmask DP in \(O(2^K \cdot K^2)\).

Solution Outline

  1. Run Dijkstra’s algorithm from each of the \((K+1)\) vertices \(S, D_1, D_2, \ldots, D_K\) to build a shortest distance table \(\mathrm{cost}[i][j]\) between all important vertices.
  2. Use bitmask DP to find the minimum cost of starting from \(S\), visiting all delivery destinations, and returning to \(S\).

Algorithm

Step 1: Building the Shortest Distance Table

Run Dijkstra’s algorithm from each of the \((K+1)\) vertices \(S, D_1, D_2, \ldots, D_K\). This gives us the shortest distance \(\mathrm{cost}[i][j]\) between any two important vertices.

For example, if \(K=3\), \(S=1\), and the delivery destinations are \(\{3, 5, 7\}\), we run Dijkstra from vertices \(1, 3, 5, 7\) and build a \(4 \times 4\) distance table.

Step 2: Bitmask DP (Traveling Salesman Problem)

  • State: \(\mathrm{dp}[\mathrm{mask}][i]\) = the minimum travel cost from \(S\) when the set of visited delivery destinations is \(\mathrm{mask}\) (managed with bits) and we are currently at delivery destination \(i\)
  • Initial state: \(\mathrm{dp}[1 \ll j][j+1] = \mathrm{cost}[0][j+1]\) (going directly from \(S\) to delivery destination \(j\))
  • Transition: When at vertex \(u\) with visited set \(\mathrm{mask}\), move to an unvisited vertex \(v\) $\(\mathrm{dp}[\mathrm{mask} \mid (1 \ll (v-1))][v] = \min\left(\mathrm{dp}[\mathrm{mask} \mid (1 \ll (v-1))][v],\; \mathrm{dp}[\mathrm{mask}][u] + \mathrm{cost}[u][v]\right)\)$
  • Answer: The minimum cost of returning to \(S\) from the state \(\mathrm{full\_mask} = 2^K - 1\) where all delivery destinations have been visited $\(\mathrm{ans} = \min_{u=1}^{K}\left(\mathrm{dp}[\mathrm{full\_mask}][u] + \mathrm{cost}[u][0]\right)\)$

Complexity

  • Time complexity: \(O((K+1) \cdot (N + M) \log N + 2^K \cdot K^2)\)
    • Running Dijkstra’s algorithm \((K+1)\) times: \(O((K+1)(N + M)\log N)\)
    • Bitmask DP: \(O(2^K \cdot K^2)\)
    • Since \(K \leq 15\), \(2^{15} \cdot 15^2 \approx 7.4 \times 10^6\), which is sufficiently fast
  • Space complexity: \(O(2^K \cdot K + (K+1) \cdot N)\)

Implementation Notes

  • Node index mapping: Manage as nodes[0] = S, nodes[1..K] = D[0..K-1], and use the bitmask only for delivery destinations (\(K\) bits). \(S\) is not included in the bitmask.

  • Dijkstra source vertices: Dijkstra must be run from both \(S\) and all delivery destinations. Running only from \(S\) would not give us the shortest distances between pairs of delivery destinations (although the graph is bidirectional so distances from \(S\) to each destination are known, distances between destinations require separate computation).

  • Bit correspondence: Delivery destination \(D_j\) (nodes[j+1]) corresponds to bit \(j\) of \(\mathrm{mask}\). Use mask & (1 << (u-1)) to check whether vertex \(u\) (1-indexed in nodes) has been visited.

    Source Code

import heapq
import sys

def dijkstra(graph, start, N):
    dist = [float('inf')] * (N + 1)
    dist[start] = 0
    pq = [(0, start)]
    while pq:
        d, u = heapq.heappop(pq)
        if d > dist[u]:
            continue
        for v, w in graph[u]:
            nd = d + w
            if nd < dist[v]:
                dist[v] = nd
                heapq.heappush(pq, (nd, v))
    return dist

def main():
    input_data = sys.stdin.buffer.read().split()
    idx = 0
    N = int(input_data[idx]); idx += 1
    M = int(input_data[idx]); idx += 1
    
    graph = [[] for _ in range(N + 1)]
    for _ in range(M):
        u = int(input_data[idx]); idx += 1
        v = int(input_data[idx]); idx += 1
        w = int(input_data[idx]); idx += 1
        graph[u].append((v, w))
        graph[v].append((u, w))
    
    S = int(input_data[idx]); idx += 1
    K = int(input_data[idx]); idx += 1
    
    D = []
    for _ in range(K):
        D.append(int(input_data[idx])); idx += 1
    
    # nodes[0] = S, nodes[1..K] = D[0..K-1]
    nodes = [S] + D
    
    # Dijkstra from each important node
    dist_from = {}
    for node in nodes:
        dist_from[node] = dijkstra(graph, node, N)
    
    # cost[i][j] = shortest distance between nodes[i] and nodes[j]
    num = K + 1  # 0 = S, 1..K = delivery points
    cost = [[0] * num for _ in range(num)]
    for i in range(num):
        for j in range(num):
            cost[i][j] = dist_from[nodes[i]][nodes[j]]
    
    # TSP with bitmask DP
    # State: (mask, i) where mask is subset of {0, 1, ..., K-1} representing visited delivery points
    # i is current position (0 = S, 1..K = delivery points)
    # We need to visit all K delivery points and return to S
    
    full_mask = (1 << K) - 1
    INF = float('inf')
    
    # dp[mask][i] = min cost to have visited delivery points in mask, currently at delivery point i (1-indexed in nodes)
    dp = [[INF] * (K + 1) for _ in range(1 << K)]
    
    # Start from S (node index 0), go to each delivery point
    for j in range(K):
        dp[1 << j][j + 1] = cost[0][j + 1]
    
    for mask in range(1, 1 << K):
        for u in range(1, K + 1):
            if dp[mask][u] == INF:
                continue
            if not (mask & (1 << (u - 1))):
                continue
            for v in range(1, K + 1):
                if mask & (1 << (v - 1)):
                    continue
                new_mask = mask | (1 << (v - 1))
                nd = dp[mask][u] + cost[u][v]
                if nd < dp[new_mask][v]:
                    dp[new_mask][v] = nd
    
    # Find minimum: visit all, then return to S
    ans = INF
    for u in range(1, K + 1):
        val = dp[full_mask][u] + cost[u][0]
        if val < ans:
            ans = val
    
    print(ans)

if __name__ == '__main__':
    main()

This editorial was generated by claude4.6opus-thinking.

投稿日時:
最終更新: