公式

E - 観光ツアーの最適ルート / Optimal Route for a Sightseeing Tour 解説 by admin

Claude 4.6 Opus (Thinking)

Overview

This is a problem on a graph consisting of \(N\) tourist spots, where you travel from start \(S\) to goal \(T\) and maximize “total satisfaction of visited tourist spots − total transportation cost of roads traversed.” The key point is that even if you visit the same spot multiple times, the satisfaction is only counted once.

Analysis

Difficulty of the Problem

This problem differs from a simple shortest path problem in the following two ways:

  1. Satisfaction for the same spot is only counted once → We need to track “which spots have been visited”
  2. Passing through the same spot multiple times is allowed → The route is not necessarily a simple path, and it may be beneficial to take a detour to visit a spot with high satisfaction

For example, we need to consider routes like \(S \to A \to B \to A \to T\), where we visit \(B\) and come back. If \(B\)’s satisfaction is high enough, the gain may increase even after paying the extra transportation cost.

Issues with a Naive Approach

Enumerating all routes causes an explosion in the number of paths since the same node can be visited multiple times. However, noting the constraint \(N \leq 12\), we can represent the “set of visited spots” as a bitmask (\(2^{12} = 4096\) possibilities).

Key Insight

If we represent the state as (current node, set of visited spots), then when the same state is reached more than once, we only need to keep the one with the smaller cost. This reduces the problem to a shortest path problem on an expanded graph.

Specifically, instead of maximizing profit = total satisfaction − total transportation cost, we transform it into minimizing total transportation cost − total satisfaction. Edge costs are positive (\(W_j \geq 1\)), and when visiting a new node, we subtract \(P_v\) (= gaining satisfaction). Since edge weights are non-negative, Dijkstra’s algorithm is applicable.

Algorithm

  1. State definition: \((u, \text{mask})\) represents “currently at node \(u\), with the set of spots visited so far being \(\text{mask}\)
  2. Cost definition: \(\text{dist}[\text{mask}][u]\) = minimum value of “total transportation cost − total satisfaction” to reach that state
  3. Initial state: \(\text{dist}[1 \ll S][S] = -P_S\) (gaining the satisfaction of the starting point)
  4. Transitions: When traversing edge \((u, v, w)\) from node \(u\):
    • If \(v\) is unvisited: new cost \(= d + w - P_v\), new mask \(= \text{mask} \mid (1 \ll v)\)
    • If \(v\) is already visited: new cost \(= d + w\), new mask \(= \text{mask}\) (no additional satisfaction)
  5. Answer: Find the minimum of \(\text{dist}[\text{mask}][T]\) over all masks \(\text{mask}\) that contain both \(S\) and \(T\), and negate its sign

In Dijkstra’s algorithm, the transition cost \(w\) (edge weight) is positive, but the effective cost can become negative when \(-P_v\) is added upon visiting a new node. However, negative costs only occur when visiting a node for the first time, and each node is visited for the first time at most once, so Dijkstra’s algorithm works correctly (negative costs arise in transitions where the mask monotonically increases, and revisiting the same state always has non-negative cost).

Complexity

  • Time complexity: \(O(2^N \cdot N \cdot (N + M) \cdot \log(2^N \cdot N))\)
    • The number of states is \(2^N \times N\), adjacent edges are explored from each state, and priority queue operations cost \(\log\)
    • Since \(N \leq 12\), there are about \(2^{12} \times 12 = 49152\) states, which is sufficiently fast
  • Space complexity: \(O(2^N \cdot N)\) (for the distance table)

Implementation Notes

  • Bitmask handling: The visited status of node \(i\) is checked with mask & (1 << i) and updated to visited with mask | (1 << i)

  • Conversion to minimization: By converting the maximization of “satisfaction − cost” to the minimization of “cost − satisfaction,” Dijkstra’s algorithm can be naturally applied

  • Recovering the answer: Finally, negate the sign of the minimum value of \(\text{dist}[\text{mask}][T]\) and output it. As long as goal \(T\) is reached, it doesn’t matter which spots were visited along the way, so we search all masks where the bits for both \(S\) and \(T\) are set

  • Conversion to 0-indexed: The input is 1-indexed, but internally it is converted to 0-indexed for bitmask management

    Source Code

import sys
from heapq import heappush, heappop

def main():
    input_data = sys.stdin.read().split()
    idx = 0
    N = int(input_data[idx]); idx += 1
    M = int(input_data[idx]); idx += 1
    
    P = [0] * N
    for i in range(N):
        P[i] = int(input_data[idx]); idx += 1
    
    S = int(input_data[idx]) - 1; idx += 1
    T = int(input_data[idx]) - 1; idx += 1
    
    # Adjacency list
    adj = [[] for _ in range(N)]
    for _ in range(M):
        u = int(input_data[idx]) - 1; idx += 1
        v = int(input_data[idx]) - 1; idx += 1
        w = int(input_data[idx]); idx += 1
        adj[u].append((v, w))
        adj[v].append((u, w))
    
    # State: (current_node, visited_set_bitmask)
    # We want to maximize: sum of P[i] for i in visited - sum of edge costs
    # Equivalent to minimizing: sum of edge costs - sum of P[i] for i in visited
    # Use Dijkstra-like approach on (node, bitmask) states
    # cost[node][mask] = minimum (total edge cost - total satisfaction) to reach node with visited set = mask
    
    INF = float('inf')
    # dist[mask][node] = min cost (edge_cost - satisfaction) 
    # We negate satisfaction: when we visit a new node, we subtract P[i]
    
    dist = [[INF] * N for _ in range(1 << N)]
    
    start_mask = 1 << S
    dist[start_mask][S] = -P[S]  # We start at S, gain P[S], no edge cost yet
    
    # Priority queue: (cost, node, mask)
    pq = []
    heappush(pq, (-P[S], S, start_mask))
    
    while pq:
        d, u, mask = heappop(pq)
        
        if d > dist[mask][u]:
            continue
        
        for v, w in adj[u]:
            new_mask = mask | (1 << v)
            # If v was already visited, no new satisfaction
            if mask & (1 << v):
                new_cost = d + w
            else:
                new_cost = d + w - P[v]
            
            if new_cost < dist[new_mask][v]:
                dist[new_mask][v] = new_cost
                heappush(pq, (new_cost, v, new_mask))
    
    # Find minimum cost over all masks that include both S and T, ending at T
    ans = INF
    required = (1 << S) | (1 << T)
    for mask in range(1 << N):
        if (mask & required) == required:
            if dist[mask][T] < ans:
                ans = dist[mask][T]
    
    # The profit is -ans (since we minimized cost - satisfaction)
    print(-ans)

main()

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

投稿日時:
最終更新: