公式

G - ボールの転送ゲーム / Ball Passing Game 解説 by admin

Claude 4.6 Opus (Thinking)

Overview

This problem asks us to efficiently determine the number of nodes visited before a revisit occurs, when a ball is passed starting from any given node on a “Functional Graph” where each node has an out-degree of exactly 1.

Analysis

Understanding the Problem Structure

Since each child \(i\) has a uniquely determined recipient \(T_i\) to pass the ball to, a graph where each node has exactly one outgoing edge (a Functional Graph) is formed.

Naive Approach: For each query, we could simulate the path starting from \(S\). In the worst case, a single simulation takes \(O(N)\), and with \(Q\) queries this becomes \(O(NQ)\). Since \(N, Q \leq 2 \times 10^5\), this might barely fit within the time limit, but we can achieve \(O(1)\) per query with precomputation.

Important Properties of Functional Graphs

Each connected component of a Functional Graph consists of exactly one cycle and tail portions that merge into that cycle. Because the shape resembles the Greek letter “ρ (rho),” it is called a ρ-shaped structure.

For example, with \(N = 6\), \(T = [2, 3, 4, 2, 4, 1]\):

5 → 4
      ↓
1 → 2 → 3 → 4 → 2 (cycle: 2→3→4→2, length 3)
6 → 1 (tail)

Answer Patterns

  • When the starting node \(S\) is on a cycle: The game ends when the ball traverses the cycle and returns to itself. Answer = cycle length.
  • When the starting node \(S\) is on a tail: The ball first follows the tail to reach the cycle, then traverses the entire cycle and returns to the cycle entry point, ending the game. Answer = distance to cycle + cycle length.

Algorithm

  1. Cycle Detection: Follow \(T\) from each node, managing three states (unvisited → in-progress → processed) to identify all cycles. Record which nodes belong to a cycle and the cycle length.

  2. Computing Distance to Cycle: Nodes on the cycle have distance 0. For other nodes, record the path while following \(T\) until reaching a node with a known distance, then determine distances in reverse order.

  3. Query Response: For a starting node \(S\), the answer is \(\text{dist}(S) + \text{cycle\_len}\), computed in \(O(1)\).

Complexity

  • Time Complexity: \(O(N + Q)\) (precomputation processes each node exactly once, and each query is \(O(1)\))
  • Space Complexity: \(O(N)\) (storing graph, distances, and cycle information)

Implementation Notes

  • In cycle detection, record the path in an array while traversing, and handle cases differently depending on whether a revisited node is “in-progress (on the current path)” or “processed (already determined by a previous traversal).”

  • For distance computation, recursion may cause stack overflow, so it is safer to iteratively follow the path and determine distances in reverse order.

  • Since the input is large, reading all at once with sys.stdin.buffer.read() improves speed.

    Source Code

import sys
from sys import setrecursionlimit

def main():
    input_data = sys.stdin.buffer.read().split()
    idx = 0
    N = int(input_data[idx]); idx += 1
    Q = int(input_data[idx]); idx += 1
    
    T = [0] * (N + 1)
    for i in range(1, N + 1):
        T[i] = int(input_data[idx]); idx += 1
    
    # Each node has out-degree 1 (functional graph).
    # For each query S, we need to find how many distinct nodes are visited
    # starting from S, following T, until we reach a node already visited.
    
    # The structure: from S, we follow a path (rho-shaped) until we revisit a node.
    # The path is S -> T[S] -> T[T[S]] -> ... until we hit a repeat.
    # The number of distinct nodes visited is the length of this path.
    
    # We can precompute for all nodes efficiently.
    # 
    # In a functional graph, each connected component has exactly one cycle.
    # For nodes on the cycle: starting from any cycle node, the answer is the cycle length.
    # For nodes on a tail (not on cycle): the answer is (tail distance to cycle) + (cycle length).
    # Wait, that's not right. Let me reconsider.
    #
    # If S is on a tail, the path goes: S -> ... -> first cycle node -> around the cycle -> 
    # back to first cycle node (already visited), stop.
    # So answer = (tail length from S to cycle entry) + (cycle length).
    #
    # If S is on the cycle, the path goes around the cycle back to S (already visited), stop.
    # So answer = cycle length.
    
    # Step 1: Find all cycles using the standard algorithm for functional graphs.
    # We'll find cycle membership and cycle lengths.
    
    visited = [0] * (N + 1)  # 0: unvisited, 1: in progress, 2: done
    on_cycle = [False] * (N + 1)
    cycle_id = [0] * (N + 1)
    cycle_len = {}
    
    current_cycle_id = 0
    
    for start in range(1, N + 1):
        if visited[start] != 0:
            continue
        path = []
        node = start
        while visited[node] == 0:
            visited[node] = 1
            path.append(node)
            node = T[node]
        
        if visited[node] == 1:
            # Found a new cycle
            current_cycle_id += 1
            # Find where the cycle starts in path
            cycle_start_idx = None
            for i, v in enumerate(path):
                if v == node:
                    cycle_start_idx = i
                    break
            clen = len(path) - cycle_start_idx
            cycle_len[current_cycle_id] = clen
            for i in range(cycle_start_idx, len(path)):
                on_cycle[path[i]] = True
                cycle_id[path[i]] = current_cycle_id
                visited[path[i]] = 2
            # Tail nodes
            for i in range(cycle_start_idx):
                visited[path[i]] = 2
        else:
            # node is already done (visited[node] == 2)
            for v in path:
                visited[v] = 2
    
    # Step 2: For each node, compute distance to cycle.
    # For cycle nodes, dist = 0, answer = cycle_len.
    # For tail nodes, answer = dist_to_cycle + cycle_len_of_that_cycle.
    
    dist = [-1] * (N + 1)
    node_cycle_id = [0] * (N + 1)
    
    for i in range(1, N + 1):
        if on_cycle[i]:
            dist[i] = 0
            node_cycle_id[i] = cycle_id[i]
    
    # BFS/DFS to compute dist for tail nodes
    # Follow T[i] until we reach a node with known dist
    for i in range(1, N + 1):
        if dist[i] != -1:
            continue
        path = []
        node = i
        while dist[node] == -1:
            path.append(node)
            node = T[node]
        # node has known dist
        d = dist[node]
        cid = node_cycle_id[node]
        for v in reversed(path):
            d += 1
            dist[v] = d
            node_cycle_id[v] = cid
    
    # Answer queries
    out = []
    for _ in range(Q):
        S = int(input_data[idx]); idx += 1
        cid = node_cycle_id[S]
        ans = dist[S] + cycle_len[cid]
        out.append(str(ans))
    
    sys.stdout.write('\n'.join(out) + '\n')

main()

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

投稿日時:
最終更新: