Functional graph DP
A functional graph gives every vertex exactly one outgoing arrow: each component is one cycle with trees leading into it, and cycle detection, distance to the cycle and k-th successor queries follow from that shape.
Learning goals
- Recognise a functional graph and describe its components
- Find all cycles and the distance of each vertex to its cycle
- Build jump pointers to find the k-th successor in O(log k)
- Use cycle arithmetic when k is huge
Prerequisites
Repeatedly applying a function, such as 'go to the next student in the chain' or 'the next state of a machine', is a walk in a graph where every vertex has exactly one outgoing arrow. Because of that single choice, the structure is very regular.
Successor links
A **functional graph** is given by an array next[v]: the successor of each vertex. Out-degree is exactly 1 everywhere; in-degree can be anything. Walking k steps from v means applying next k times. Doing it step by step costs O(k) per query, fine for small k and too slow for k in the millions.
A walk must eventually repeat a vertex (only n vertices exist) and then repeats forever, because the next vertex is forced. That tail-then-cycle shape (the Greek letter rho) is the key to everything that follows.
1static int[] inDegrees(int[] next) { // how many vertices point at each vertex2int[] in = new int[next.length];3for (int v : next) in[v]++;4return in;5}6static int walkNaive(int[] next, int v, long k) { // O(k): apply the successor k times7for (long i = 0; i < k; i++) v = next[v];8return v;9}
Functional graphs: one successor per vertex, cycles with trees, jump pointers
Interactive · edit the inputdirected graph · 8 vertices · 8 edges
steps to reach a cycle
Every vertex has exactly one outgoing arrow, so walking from any vertex either reaches a vertex seen before. Each connected piece is one cycle with trees hanging into it.
1static int[] stepsToCycle(int[] next) { // 0 for vertices on a cycle2int n = next.length;3int[] state = new int[n], dist = new int[n]; // state: 0 new, 1 on the current walk, 2 finished4for (int s = 0; s < n; s++) {5if (state[s] != 0) continue;6List<Integer> walk = new ArrayList<>();7int v = s;8while (state[v] == 0) { state[v] = 1; walk.add(v); v = next[v]; }9int from = walk.size(); // answers are filled backwards from here10if (state[v] == 1) { // the walk met itself: a new cycle11int at = walk.indexOf(v);12for (int i = at; i < walk.size(); i++) dist[walk.get(i)] = 0;13from = at;14}15for (int i = from - 1; i >= 0; i--) dist[walk.get(i)] = dist[next[walk.get(i)]] + 1;16for (int u : walk) state[u] = 2;17}18return dist;19}20static int[][] jumpTable(int[] next, int levels) { // up[j][v] = the vertex reached after 2^j steps21int n = next.length;22int[][] up = new int[levels][];23up[0] = next.clone();24for (int j = 1; j < levels; j++) {25up[j] = new int[n];26for (int v = 0; v < n; v++) up[j][v] = up[j - 1][up[j - 1][v]];27}28return up;29}30static int kthSuccessor(int[][] up, int v, long k) {31for (int j = 0; j < up.length; j++) if ((k >> j & 1) == 1) v = up[j][v];32return v;33}
Live state
n
8
Execution trace (dry run table)Show 22 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | n=8 | Every vertex has exactly one outgoing arrow, so walking from any vertex either reaches a vertex seen before. Each connected piece is one cycle with trees hanging into it. |
| 2 | L8 | start=0 walk=[0] | Walk from 0: mark 0, then follow its arrow to 1. |
| 3 | L8 | start=0 walk=[0, 1] | Walk from 0: mark 1, then follow its arrow to 2. |
| 4 | L8 | start=0 walk=[0, 1, 2] | Walk from 0: mark 2, then follow its arrow to 0. |
| 5 | L11 | cycle=[0, 1, 2] | The walk reached 0, which is on the current walk: the vertices [0, 1, 2] form a cycle of length 3. Their distance to the cycle is 0. |
| 6 | L15 | distances=[0, 0, 0] | No tail: every vertex of this walk is on the cycle. |
| 7 | L8 | start=3 walk=[3] | Walk from 3: mark 3, then follow its arrow to 2. |
| 8 | L11 | met=2 | The walk reached 2, which was finished earlier: the new vertices lead into it. |
| 9 | L15 | distances=[0, 0, 0, 1] | Fill the tail backwards: each tail vertex is one step further from the cycle than its successor. |
| 10 | L8 | start=4 walk=[4] | Walk from 4: mark 4, then follow its arrow to 3. |
| 11 | L11 | met=3 | The walk reached 3, which was finished earlier: the new vertices lead into it. |
| 12 | L15 | distances=[0, 0, 0, 1, 2] | Fill the tail backwards: each tail vertex is one step further from the cycle than its successor. |
| 13 | L8 | start=5 walk=[5] | Walk from 5: mark 5, then follow its arrow to 4. |
| 14 | L11 | met=4 | The walk reached 4, which was finished earlier: the new vertices lead into it. |
| 15 | L15 | distances=[0, 0, 0, 1, 2, 3] | Fill the tail backwards: each tail vertex is one step further from the cycle than its successor. |
| 16 | L8 | start=6 walk=[6] | Walk from 6: mark 6, then follow its arrow to 6. |
| 17 | L11 | cycle=[6] | The walk reached 6, which is on the current walk: the vertices [6] form a cycle of length 1. Their distance to the cycle is 0. |
| 18 | L15 | distances=[0, 0, 0, 1, 2, 3, 0] | No tail: every vertex of this walk is on the cycle. |
| 19 | L8 | start=7 walk=[7] | Walk from 7: mark 7, then follow its arrow to 6. |
| 20 | L11 | met=6 | The walk reached 6, which was finished earlier: the new vertices lead into it. |
| 21 | L15 | distances=[0, 0, 0, 1, 2, 3, 0, 1] | Fill the tail backwards: each tail vertex is one step further from the cycle than its successor. |
| 22 | L18 | return=[0, 0, 0, 1, 2, 3, 0, 1] cycles=2 | 2 cycles found. Distances to the cycle: [0, 0, 0, 1, 2, 3, 0, 1] (0 = on a cycle). Each vertex is walked once: O(n). |
Cycles with in-trees
Every weakly connected component of a functional graph has **exactly one cycle** (possibly a self-loop), and trees whose arrows point towards the cycle, called *in-trees*. To find the cycles, walk from each unvisited vertex marking the current walk; if the walk meets a vertex of the current walk, that part is a new cycle; if it meets a finished vertex, the walk only added a tail.
Every vertex is walked once, so the total cost is O(n). The animation draws vertices on a cycle in green and shows the walk in progress; the second preset has one cycle through all vertices, the third has self-loops.
1static List<List<Integer>> cycles(int[] next) { // every cycle, each listed in order along the arrows2int n = next.length;3int[] state = new int[n]; // 0 new, 1 on the current walk, 2 finished4List<List<Integer>> found = new ArrayList<>();5for (int s = 0; s < n; s++) {6if (state[s] != 0) continue;7List<Integer> walk = new ArrayList<>();8int v = s;9while (state[v] == 0) { state[v] = 1; walk.add(v); v = next[v]; }10if (state[v] == 1) found.add(new ArrayList<>(walk.subList(walk.indexOf(v), walk.size()))); // the walk met itself11for (int u : walk) state[u] = 2;12}13return found;14}
Functional graphs: one successor per vertex, cycles with trees, jump pointers
Interactive · edit the inputdirected graph · 8 vertices · 8 edges
steps to reach a cycle
Every vertex has exactly one outgoing arrow, so walking from any vertex either reaches a vertex seen before. Each connected piece is one cycle with trees hanging into it.
1static int[] stepsToCycle(int[] next) { // 0 for vertices on a cycle2int n = next.length;3int[] state = new int[n], dist = new int[n]; // state: 0 new, 1 on the current walk, 2 finished4for (int s = 0; s < n; s++) {5if (state[s] != 0) continue;6List<Integer> walk = new ArrayList<>();7int v = s;8while (state[v] == 0) { state[v] = 1; walk.add(v); v = next[v]; }9int from = walk.size(); // answers are filled backwards from here10if (state[v] == 1) { // the walk met itself: a new cycle11int at = walk.indexOf(v);12for (int i = at; i < walk.size(); i++) dist[walk.get(i)] = 0;13from = at;14}15for (int i = from - 1; i >= 0; i--) dist[walk.get(i)] = dist[next[walk.get(i)]] + 1;16for (int u : walk) state[u] = 2;17}18return dist;19}20static int[][] jumpTable(int[] next, int levels) { // up[j][v] = the vertex reached after 2^j steps21int n = next.length;22int[][] up = new int[levels][];23up[0] = next.clone();24for (int j = 1; j < levels; j++) {25up[j] = new int[n];26for (int v = 0; v < n; v++) up[j][v] = up[j - 1][up[j - 1][v]];27}28return up;29}30static int kthSuccessor(int[][] up, int v, long k) {31for (int j = 0; j < up.length; j++) if ((k >> j & 1) == 1) v = up[j][v];32return v;33}
Live state
n
8
Execution trace (dry run table)Show 22 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | n=8 | Every vertex has exactly one outgoing arrow, so walking from any vertex either reaches a vertex seen before. Each connected piece is one cycle with trees hanging into it. |
| 2 | L8 | start=0 walk=[0] | Walk from 0: mark 0, then follow its arrow to 1. |
| 3 | L8 | start=0 walk=[0, 1] | Walk from 0: mark 1, then follow its arrow to 2. |
| 4 | L8 | start=0 walk=[0, 1, 2] | Walk from 0: mark 2, then follow its arrow to 0. |
| 5 | L11 | cycle=[0, 1, 2] | The walk reached 0, which is on the current walk: the vertices [0, 1, 2] form a cycle of length 3. Their distance to the cycle is 0. |
| 6 | L15 | distances=[0, 0, 0] | No tail: every vertex of this walk is on the cycle. |
| 7 | L8 | start=3 walk=[3] | Walk from 3: mark 3, then follow its arrow to 2. |
| 8 | L11 | met=2 | The walk reached 2, which was finished earlier: the new vertices lead into it. |
| 9 | L15 | distances=[0, 0, 0, 1] | Fill the tail backwards: each tail vertex is one step further from the cycle than its successor. |
| 10 | L8 | start=4 walk=[4] | Walk from 4: mark 4, then follow its arrow to 3. |
| 11 | L11 | met=3 | The walk reached 3, which was finished earlier: the new vertices lead into it. |
| 12 | L15 | distances=[0, 0, 0, 1, 2] | Fill the tail backwards: each tail vertex is one step further from the cycle than its successor. |
| 13 | L8 | start=5 walk=[5] | Walk from 5: mark 5, then follow its arrow to 4. |
| 14 | L11 | met=4 | The walk reached 4, which was finished earlier: the new vertices lead into it. |
| 15 | L15 | distances=[0, 0, 0, 1, 2, 3] | Fill the tail backwards: each tail vertex is one step further from the cycle than its successor. |
| 16 | L8 | start=6 walk=[6] | Walk from 6: mark 6, then follow its arrow to 6. |
| 17 | L11 | cycle=[6] | The walk reached 6, which is on the current walk: the vertices [6] form a cycle of length 1. Their distance to the cycle is 0. |
| 18 | L15 | distances=[0, 0, 0, 1, 2, 3, 0] | No tail: every vertex of this walk is on the cycle. |
| 19 | L8 | start=7 walk=[7] | Walk from 7: mark 7, then follow its arrow to 6. |
| 20 | L11 | met=6 | The walk reached 6, which was finished earlier: the new vertices lead into it. |
| 21 | L15 | distances=[0, 0, 0, 1, 2, 3, 0, 1] | Fill the tail backwards: each tail vertex is one step further from the cycle than its successor. |
| 22 | L18 | return=[0, 0, 0, 1, 2, 3, 0, 1] cycles=2 | 2 cycles found. Distances to the cycle: [0, 0, 0, 1, 2, 3, 0, 1] (0 = on a cycle). Each vertex is walked once: O(n). |
Jump pointers
To find the k-th successor quickly, precompute up[j][v]: where v lands after 2^j steps. up[0] is next, and up[j][v] = up[j-1][up[j-1][v]], since two jumps of 2^(j-1) make one of 2^j. To jump k steps, write k in binary and apply the jump for each 1 bit.
Building costs O(n log k) and each query O(log k). The second option of the animation builds the table row by row and then applies the bits of k. This is the same binary lifting used for lowest common ancestors in trees.
1static int[][] jumpTable(int[] next, int levels) { // up[j][v] = the vertex reached after 2^j steps2int n = next.length;3int[][] up = new int[levels][];4up[0] = next.clone();5for (int j = 1; j < levels; j++) {6up[j] = new int[n];7for (int v = 0; v < n; v++) up[j][v] = up[j - 1][up[j - 1][v]];8}9return up;10}11static int kthSuccessor(int[][] up, int v, long k) { // k must be below 2^levels12for (int j = 0; j < up.length; j++) if ((k >> j & 1) == 1) v = up[j][v];13return v;14}
Functional graphs: one successor per vertex, cycles with trees, jump pointers
Interactive · edit the inputdirected graph · 8 vertices · 8 edges
steps to reach a cycle
Every vertex has exactly one outgoing arrow, so walking from any vertex either reaches a vertex seen before. Each connected piece is one cycle with trees hanging into it.
1static int[] stepsToCycle(int[] next) { // 0 for vertices on a cycle2int n = next.length;3int[] state = new int[n], dist = new int[n]; // state: 0 new, 1 on the current walk, 2 finished4for (int s = 0; s < n; s++) {5if (state[s] != 0) continue;6List<Integer> walk = new ArrayList<>();7int v = s;8while (state[v] == 0) { state[v] = 1; walk.add(v); v = next[v]; }9int from = walk.size(); // answers are filled backwards from here10if (state[v] == 1) { // the walk met itself: a new cycle11int at = walk.indexOf(v);12for (int i = at; i < walk.size(); i++) dist[walk.get(i)] = 0;13from = at;14}15for (int i = from - 1; i >= 0; i--) dist[walk.get(i)] = dist[next[walk.get(i)]] + 1;16for (int u : walk) state[u] = 2;17}18return dist;19}20static int[][] jumpTable(int[] next, int levels) { // up[j][v] = the vertex reached after 2^j steps21int n = next.length;22int[][] up = new int[levels][];23up[0] = next.clone();24for (int j = 1; j < levels; j++) {25up[j] = new int[n];26for (int v = 0; v < n; v++) up[j][v] = up[j - 1][up[j - 1][v]];27}28return up;29}30static int kthSuccessor(int[][] up, int v, long k) {31for (int j = 0; j < up.length; j++) if ((k >> j & 1) == 1) v = up[j][v];32return v;33}
Live state
n
8
Execution trace (dry run table)Show 22 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | n=8 | Every vertex has exactly one outgoing arrow, so walking from any vertex either reaches a vertex seen before. Each connected piece is one cycle with trees hanging into it. |
| 2 | L8 | start=0 walk=[0] | Walk from 0: mark 0, then follow its arrow to 1. |
| 3 | L8 | start=0 walk=[0, 1] | Walk from 0: mark 1, then follow its arrow to 2. |
| 4 | L8 | start=0 walk=[0, 1, 2] | Walk from 0: mark 2, then follow its arrow to 0. |
| 5 | L11 | cycle=[0, 1, 2] | The walk reached 0, which is on the current walk: the vertices [0, 1, 2] form a cycle of length 3. Their distance to the cycle is 0. |
| 6 | L15 | distances=[0, 0, 0] | No tail: every vertex of this walk is on the cycle. |
| 7 | L8 | start=3 walk=[3] | Walk from 3: mark 3, then follow its arrow to 2. |
| 8 | L11 | met=2 | The walk reached 2, which was finished earlier: the new vertices lead into it. |
| 9 | L15 | distances=[0, 0, 0, 1] | Fill the tail backwards: each tail vertex is one step further from the cycle than its successor. |
| 10 | L8 | start=4 walk=[4] | Walk from 4: mark 4, then follow its arrow to 3. |
| 11 | L11 | met=3 | The walk reached 3, which was finished earlier: the new vertices lead into it. |
| 12 | L15 | distances=[0, 0, 0, 1, 2] | Fill the tail backwards: each tail vertex is one step further from the cycle than its successor. |
| 13 | L8 | start=5 walk=[5] | Walk from 5: mark 5, then follow its arrow to 4. |
| 14 | L11 | met=4 | The walk reached 4, which was finished earlier: the new vertices lead into it. |
| 15 | L15 | distances=[0, 0, 0, 1, 2, 3] | Fill the tail backwards: each tail vertex is one step further from the cycle than its successor. |
| 16 | L8 | start=6 walk=[6] | Walk from 6: mark 6, then follow its arrow to 6. |
| 17 | L11 | cycle=[6] | The walk reached 6, which is on the current walk: the vertices [6] form a cycle of length 1. Their distance to the cycle is 0. |
| 18 | L15 | distances=[0, 0, 0, 1, 2, 3, 0] | No tail: every vertex of this walk is on the cycle. |
| 19 | L8 | start=7 walk=[7] | Walk from 7: mark 7, then follow its arrow to 6. |
| 20 | L11 | met=6 | The walk reached 6, which was finished earlier: the new vertices lead into it. |
| 21 | L15 | distances=[0, 0, 0, 1, 2, 3, 0, 1] | Fill the tail backwards: each tail vertex is one step further from the cycle than its successor. |
| 22 | L18 | return=[0, 0, 0, 1, 2, 3, 0, 1] cycles=2 | 2 cycles found. Distances to the cycle: [0, 0, 0, 1, 2, 3, 0, 1] (0 = on a cycle). Each vertex is walked once: O(n). |
Path length to cycle
The **distance to the cycle** of a vertex is the number of steps until the walk first lands on a cycle vertex: 0 on the cycle itself, and 1 + distance(next[v]) otherwise. Fill it in during the cycle search, backwards along each walk, so every vertex is computed once.
With the tail length and the cycle length, even a huge k is easy: after the t steps of the tail, the position repeats every c steps, so the k-th vertex of the walk is the vertex at index t + (k - t) mod c. This costs O(n) per query with no table.
1static int[] stepsToCycle(int[] next) { // 0 for vertices on a cycle2int n = next.length;3int[] state = new int[n], dist = new int[n]; // state: 0 new, 1 on the current walk, 2 finished4for (int s = 0; s < n; s++) {5if (state[s] != 0) continue;6List<Integer> walk = new ArrayList<>();7int v = s;8while (state[v] == 0) { state[v] = 1; walk.add(v); v = next[v]; }9int from = walk.size(); // answers are filled backwards from here10if (state[v] == 1) { // the walk met itself: a new cycle11int at = walk.indexOf(v);12for (int i = at; i < walk.size(); i++) dist[walk.get(i)] = 0;13from = at;14}15for (int i = from - 1; i >= 0; i--) dist[walk.get(i)] = dist[next[walk.get(i)]] + 1;16for (int u : walk) state[u] = 2;17}18return dist;19}20static int kthByCycleMath(int[] next, int v, long k) { // O(n) per query, no table21int n = next.length;22int[] firstSeen = new int[n];23Arrays.fill(firstSeen, -1);24List<Integer> path = new ArrayList<>();25int x = v;26while (firstSeen[x] == -1) { firstSeen[x] = path.size(); path.add(x); x = next[x]; }27int tail = firstSeen[x], cycle = path.size() - tail; // x is the first vertex seen twice28if (k < path.size()) return path.get((int) k);29return path.get(tail + (int) ((k - tail) % cycle));30}
Functional graphs: one successor per vertex, cycles with trees, jump pointers
Interactive · edit the inputdirected graph · 8 vertices · 8 edges
steps to reach a cycle
Every vertex has exactly one outgoing arrow, so walking from any vertex either reaches a vertex seen before. Each connected piece is one cycle with trees hanging into it.
1static int[] stepsToCycle(int[] next) { // 0 for vertices on a cycle2int n = next.length;3int[] state = new int[n], dist = new int[n]; // state: 0 new, 1 on the current walk, 2 finished4for (int s = 0; s < n; s++) {5if (state[s] != 0) continue;6List<Integer> walk = new ArrayList<>();7int v = s;8while (state[v] == 0) { state[v] = 1; walk.add(v); v = next[v]; }9int from = walk.size(); // answers are filled backwards from here10if (state[v] == 1) { // the walk met itself: a new cycle11int at = walk.indexOf(v);12for (int i = at; i < walk.size(); i++) dist[walk.get(i)] = 0;13from = at;14}15for (int i = from - 1; i >= 0; i--) dist[walk.get(i)] = dist[next[walk.get(i)]] + 1;16for (int u : walk) state[u] = 2;17}18return dist;19}20static int[][] jumpTable(int[] next, int levels) { // up[j][v] = the vertex reached after 2^j steps21int n = next.length;22int[][] up = new int[levels][];23up[0] = next.clone();24for (int j = 1; j < levels; j++) {25up[j] = new int[n];26for (int v = 0; v < n; v++) up[j][v] = up[j - 1][up[j - 1][v]];27}28return up;29}30static int kthSuccessor(int[][] up, int v, long k) {31for (int j = 0; j < up.length; j++) if ((k >> j & 1) == 1) v = up[j][v];32return v;33}
Live state
n
8
Execution trace (dry run table)Show 22 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | n=8 | Every vertex has exactly one outgoing arrow, so walking from any vertex either reaches a vertex seen before. Each connected piece is one cycle with trees hanging into it. |
| 2 | L8 | start=0 walk=[0] | Walk from 0: mark 0, then follow its arrow to 1. |
| 3 | L8 | start=0 walk=[0, 1] | Walk from 0: mark 1, then follow its arrow to 2. |
| 4 | L8 | start=0 walk=[0, 1, 2] | Walk from 0: mark 2, then follow its arrow to 0. |
| 5 | L11 | cycle=[0, 1, 2] | The walk reached 0, which is on the current walk: the vertices [0, 1, 2] form a cycle of length 3. Their distance to the cycle is 0. |
| 6 | L15 | distances=[0, 0, 0] | No tail: every vertex of this walk is on the cycle. |
| 7 | L8 | start=3 walk=[3] | Walk from 3: mark 3, then follow its arrow to 2. |
| 8 | L11 | met=2 | The walk reached 2, which was finished earlier: the new vertices lead into it. |
| 9 | L15 | distances=[0, 0, 0, 1] | Fill the tail backwards: each tail vertex is one step further from the cycle than its successor. |
| 10 | L8 | start=4 walk=[4] | Walk from 4: mark 4, then follow its arrow to 3. |
| 11 | L11 | met=3 | The walk reached 3, which was finished earlier: the new vertices lead into it. |
| 12 | L15 | distances=[0, 0, 0, 1, 2] | Fill the tail backwards: each tail vertex is one step further from the cycle than its successor. |
| 13 | L8 | start=5 walk=[5] | Walk from 5: mark 5, then follow its arrow to 4. |
| 14 | L11 | met=4 | The walk reached 4, which was finished earlier: the new vertices lead into it. |
| 15 | L15 | distances=[0, 0, 0, 1, 2, 3] | Fill the tail backwards: each tail vertex is one step further from the cycle than its successor. |
| 16 | L8 | start=6 walk=[6] | Walk from 6: mark 6, then follow its arrow to 6. |
| 17 | L11 | cycle=[6] | The walk reached 6, which is on the current walk: the vertices [6] form a cycle of length 1. Their distance to the cycle is 0. |
| 18 | L15 | distances=[0, 0, 0, 1, 2, 3, 0] | No tail: every vertex of this walk is on the cycle. |
| 19 | L8 | start=7 walk=[7] | Walk from 7: mark 7, then follow its arrow to 6. |
| 20 | L11 | met=6 | The walk reached 6, which was finished earlier: the new vertices lead into it. |
| 21 | L15 | distances=[0, 0, 0, 1, 2, 3, 0, 1] | Fill the tail backwards: each tail vertex is one step further from the cycle than its successor. |
| 22 | L18 | return=[0, 0, 0, 1, 2, 3, 0, 1] cycles=2 | 2 cycles found. Distances to the cycle: [0, 0, 0, 1, 2, 3, 0, 1] (0 = on a cycle). Each vertex is walked once: O(n). |
Practice ladder
Now try it yourself
Easy
- Linked List Cycle
Medium
- Linked List Cycle II
- Find the Duplicate Number
- Longest Cycle in a Graph
Advanced
- Maximum Employees to Be Invited to a Meeting
- Count Visited Nodes in a Directed Graph
- Kth Ancestor of a Tree Node
Key takeaways
- A functional graph has one outgoing arrow per vertex; each component is a cycle with trees pointing into it.
- Find cycles by walking and marking; every vertex is walked once, O(n).
- Binary lifting: up[j][v] after 2^j steps; k-th successor in O(log k).
- With the tail and cycle lengths, the k-th vertex is at index tail + (k - tail) mod cycle for large k.