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
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.
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.
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.
投稿日時:
最終更新: