公式

H - 都市の巡回調査 / City Tour Survey 解説 by admin

Claude 4.6 Opus (Thinking)

Overview

This is a problem where you simulate the order of visiting all cities on a directed graph following specific rules. At each tour, you greedily choose the “unvisited adjacent city with the highest importance,” and when there are no more destinations, you start a new tour.

Analysis

Problem Breakdown

Organizing the procedure, the following two operations alternate:

  1. Starting a tour: The first tour starts from city \(1\); subsequent tours start from the unvisited city with the highest importance.
  2. Moving during a tour: Among the unvisited cities reachable via edges from the current city, move to the one with the highest importance. If there is no such city, the tour ends.

Issues with the Naive Approach

If we naively search for “the unvisited adjacent city with the highest importance” at each step, we need to scan the entire adjacency list every time. In the worst case, we end up checking the same edges repeatedly, resulting in \(O(N \times M)\) time complexity with the risk of TLE.

Optimization Idea

Pre-sort each adjacency list in descending order of importance, and maintain a pointer (cursor) for each vertex. The pointer indicates “the next position to check,” and elements already determined to be visited are never revisited. This ensures the total scan across all adjacency lists is bounded by \(O(M)\).

Algorithm

  1. Preprocessing: Sort each vertex’s adjacency list adj[u] in descending order of the destination’s importance \(B[v]\). Also create a reverse lookup array imp_to_city mapping importance to city number.

  2. Managing tour starting cities: Insert all cities (except city \(1\)) into a max-heap of importance (in Python, a min-heap using negated values). When starting a new tour, pop from the heap, and if the city is unvisited, start the tour from that city.

  3. Moving during a tour (pointer method):

    • Prepare a pointer ptr[u] for each vertex \(u\) (initialized to \(0\)).
    • Scan the adjacency list of the current city current starting from position ptr[current].
    • Skip visited cities and advance ptr[current].
    • If an unvisited city is found, move there and mark it as visited.
    • If none is found, the tour ends.
  4. Repeat until all cities are visited.

Concrete Example

Given \(4\) cities, edges \(1 \to 2, 1 \to 3, 2 \to 4\), and importance \(B = [3, 4, 1, 2]\) (city 1 has importance 3, city 2 has importance 4, …):

  • 1st tour: Start from city \(1\) → adjacent unvisited cities are \(\{2, 3\}\), with importance \(B[2]=4, B[3]=1\), so move to city \(2\) → city \(2\)’s adjacent unvisited is \(\{4\}\), so move to city \(4\) → city \(4\) has no adjacent unvisited → tour ends. Visit order: \(1, 2, 4\)
  • 2nd tour: The unvisited city with the highest importance is city \(3\) (importance \(1\)) → no adjacent unvisited → tour ends. Visit order: \(3\)
  • Output: 1 2 4 3

Complexity

  • Time complexity: \(O((N + M) \log N)\)
    • Sorting adjacency lists: \(O(M \log N)\)
    • Heap operations: \(O(N \log N)\)
    • Adjacency list traversal via pointers: each edge is skipped at most once, so \(O(M)\) total
  • Space complexity: \(O(N + M)\)

Implementation Notes

  • The pointer method is the key: By using ptr[u], we avoid re-checking previously skipped visited vertices. Since the sorted list has higher importance toward the front, the first unvisited city found while advancing the pointer is directly the candidate with the highest importance.

  • Max-heap workaround: Since Python’s heapq is a min-heap, we insert with negated importance (-B[i]) to simulate a max-heap.

  • The first tour is special: Note that the starting city is fixed as city \(1\) rather than “the unvisited city with the highest importance.” This is controlled by the first_tour flag.

    Source Code

import sys
from heapq import heappush, heappop

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
    
    B = [0] * (N + 1)
    for i in range(1, N + 1):
        B[i] = int(input_data[idx]); idx += 1
    
    adj = [[] for _ in range(N + 1)]
    for _ in range(M):
        u = int(input_data[idx]); idx += 1
        v = int(input_data[idx]); idx += 1
        adj[u].append(v)
    
    # For each city, sort neighbors by importance descending for efficient traversal
    # We'll use a max-heap (negate values) for neighbors
    
    visited = [False] * (N + 1)
    result = []
    
    # We need to efficiently find the unvisited city with max importance for new tours
    # Use a max-heap of all cities by importance
    # For the first tour, start city is 1
    # For subsequent tours, pick unvisited city with max importance
    
    # For adjacency, we need to pick unvisited neighbor with max importance
    # We can use a heap per node, but that's expensive
    # Instead, sort adjacency lists by importance descending, and use a heap during traversal
    
    # Sort adjacency lists by B[v] descending
    for u in range(N + 1):
        adj[u].sort(key=lambda v: -B[v])
    
    # Global heap for picking start cities (max-heap by importance)
    # We'll add all cities except city 1
    global_heap = []
    for i in range(1, N + 1):
        if i != 1:
            heappush(global_heap, -B[i])
    
    # Map from importance to city
    imp_to_city = [0] * (N + 1)
    for i in range(1, N + 1):
        imp_to_city[B[i]] = i
    
    # First tour starts at city 1
    first_tour = True
    start_city = 1
    
    while True:
        if first_tour:
            current = start_city
            first_tour = False
        else:
            # Find unvisited city with max importance
            current = -1
            while global_heap:
                neg_imp = heappop(global_heap)
                imp = -neg_imp
                city = imp_to_city[imp]
                if not visited[city]:
                    current = city
                    break
            if current == -1:
                break
        
        if visited[current]:
            # This shouldn't happen for first tour if city 1 is valid
            # For subsequent tours, we already checked
            continue
        
        # Start tour from current
        visited[current] = True
        result.append(current)
        
        # Use pointer-based approach for adjacency lists
        # For each node, maintain a pointer into its sorted adjacency list
        # But we need to skip visited nodes
        
        while True:
            # Find unvisited neighbor with max importance
            found = False
            best_neighbor = -1
            best_imp = -1
            
            for v in adj[current]:
                if not visited[v]:
                    if B[v] > best_imp:
                        best_imp = B[v]
                        best_neighbor = v
                        # Since sorted descending, first unvisited is the best
                        found = True
                        break
            
            if not found:
                break
            
            current = best_neighbor
            visited[current] = True
            result.append(current)
        
        if len(result) == N:
            break
    
    sys.stdout.write(' '.join(map(str, result)) + '\n')

# The above has worst case O(N*M) due to scanning adjacency lists
# Let's optimize with pointers into sorted adjacency lists

def main_optimized():
    input_data = sys.stdin.buffer.read().split()
    idx = 0
    N = int(input_data[idx]); idx += 1
    M = int(input_data[idx]); idx += 1
    
    B = [0] * (N + 1)
    for i in range(1, N + 1):
        B[i] = int(input_data[idx]); idx += 1
    
    adj = [[] for _ in range(N + 1)]
    for _ in range(M):
        u = int(input_data[idx]); idx += 1
        v = int(input_data[idx]); idx += 1
        adj[u].append(v)
    
    for u in range(N + 1):
        adj[u].sort(key=lambda v: -B[v])
    
    imp_to_city = [0] * (N + 1)
    for i in range(1, N + 1):
        imp_to_city[B[i]] = i
    
    visited = [False] * (N + 1)
    ptr = [0] * (N + 1)  # pointer into adj[u]
    result = []
    
    global_heap = []
    for i in range(1, N + 1):
        if i != 1:
            heappush(global_heap, -B[i])
    
    first_tour = True
    
    while len(result) < N:
        if first_tour:
            current = 1
            first_tour = False
        else:
            current = -1
            while global_heap:
                imp = -heappop(global_heap)
                city = imp_to_city[imp]
                if not visited[city]:
                    current = city
                    break
            if current == -1:
                break
        
        if visited[current]:
            continue
        
        visited[current] = True
        result.append(current)
        
        while True:
            nxt = -1
            while ptr[current] < len(adj[current]):
                v = adj[current][ptr[current]]
                if not visited[v]:
                    nxt = v
                    break
                ptr[current] += 1
            
            if nxt == -1:
                break
            
            current = nxt
            visited[current] = True
            result.append(current)
    
    sys.stdout.write(' '.join(map(str, result)) + '\n')

main_optimized()

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

投稿日時:
最終更新: