Binary Lifting Successor
You're standing on node \(x\) in a functional graph. Someone asks: "Where will you be after \(10^9\) steps?" Following edges one by one would take a billion operations. Binary lifting answers in 30.
The Problem
![Binary lifting table: up[k][v] stores the 2^k-th successor of v](../../../assets/images/graph-course/img_16_02_binary_lifting.png)
Given a functional graph \(f: \{1,\ldots,N\} \to \{1,\ldots,N\}\) and queries \((x, k)\), find \(f^{(k)}(x)\) -- the node reached by applying \(f\) exactly \(k\) times starting from \(x\).
Naive approach: follow \(f\) one step at a time. Time per query: \(O(k)\). When \(k = 10^9\), that's way too slow.
The Binary Lifting Idea
Precompute power-of-two jumps:
- \(\text{up}[x][0] = f(x)\) -- the \(2^0 = 1\)-step successor.
- \(\text{up}[x][1] = f(f(x))\) -- the \(2^1 = 2\)-step successor.
- $\text{up}[x][j] = $ the \(2^j\)-step successor of \(x\).
The recurrence is simple: \(\text{up}[x][j] = \text{up}[\text{up}[x][j-1]][j-1]\).
To jump \(2^j\) steps, first jump \(2^{j-1}\) steps, then jump \(2^{j-1}\) more.
const int LOG = 30;
vector<vector<int>> up(n + 1, vector<int>(LOG));
for (int i = 1; i <= n; i++) up[i][0] = f[i];
for (int j = 1; j < LOG; j++)
for (int i = 1; i <= n; i++)
up[i][j] = up[up[i][j-1]][j-1];
Precomputation: \(O(N \log K)\) time and space, where \(K\) is the maximum query value.
Answering a Query

Decompose \(k\) into binary. For each set bit \(j\), jump \(2^j\) steps.
int query(int x, int k) {
for (int j = 0; j < LOG; j++) {
if (k & (1 << j)) {
x = up[x][j];
}
}
return x;
}
Time per query: \(O(\log K)\).
Trace
Function: \(f = [-, 3, 1, 4, 2, 6, 5]\) (\(N = 6\)). Query: start at node 1, take \(k = 5\) steps.
First, the precomputation table:
| Node | up[·][0] | up[·][1] | up[·][2] |
|---|---|---|---|
| 1 | 3 | 4 | 1 |
| 2 | 1 | 3 | 4 |
| 3 | 4 | 2 | 3 |
| 4 | 2 | 3 | 4 |
| 5 | 6 | 5 | 6 |
| 6 | 5 | 6 | 5 |
Query \((x=1, k=5)\). Binary of 5 = 101.
| Bit \(j\) | Bit set? | Action | Current node |
|---|---|---|---|
| 0 | Yes | \(x = \text{up}[1][0] = 3\) | 3 |
| 1 | No | Skip | 3 |
| 2 | Yes | \(x = \text{up}[3][2] = 4\) | 4 |
Verify by following \(f\) manually: \(1 \to 3 \to 4 \to 2 \to 1 \to 3\). After 5 steps: node 3. Wait, that doesn't match.
Let me recheck. \(f(1) = 3\), \(f(3) = 4\), \(f(4) = 2\), \(f(2) = 1\), \(f(1) = 3\). So after 5 steps from 1: node 3.
The table: $\text{up}[3][2] = $ 4 steps from 3 = \(3 \to 4 \to 2 \to 1 \to 3\). That's node 3, not 4. Let me recompute.
\(\text{up}[3][0] = 4\). \(\text{up}[3][1] = \text{up}[\text{up}[3][0]][0] = \text{up}[4][0] = 2\). \(\text{up}[3][2] = \text{up}[\text{up}[3][1]][1] = \text{up}[2][1] = 3\).
Corrected table:
| Node | up[·][0] | up[·][1] | up[·][2] |
|---|---|---|---|
| 1 | 3 | 4 | 1 |
| 2 | 1 | 3 | 2 |
| 3 | 4 | 2 | 3 |
| 4 | 2 | 1 | 4 |
Query \((x=1, k=5)\). \(k = 101_2\).
| Bit \(j\) | Bit set? | Action | Current node |
|---|---|---|---|
| 0 | Yes | \(x = \text{up}[1][0] = 3\) | 3 |
| 2 | Yes | \(x = \text{up}[3][2] = 3\) | 3 |
Answer: 3. Matches the manual trace.
Key insight. Binary lifting is the same idea as fast exponentiation. Instead of multiplying a number, you're composing a function. Any \(k\) can be decomposed into at most \(\log k\) powers of 2, so \(\log k\) lookups suffice.
CSES Planets Queries I
Problem. \(N\) planets, each with a teleporter to one other planet. \(Q\) queries: starting at planet \(x\), where are you after \(k\) teleportations?
This is the exact problem binary lifting solves.
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, q;
scanf("%d%d", &n, &q);
const int LOG = 30;
vector<vector<int>> up(n + 1, vector<int>(LOG));
for (int i = 1; i <= n; i++) scanf("%d", &up[i][0]);
for (int j = 1; j < LOG; j++)
for (int i = 1; i <= n; i++)
up[i][j] = up[up[i][j - 1]][j - 1];
while (q--) {
int x, k;
scanf("%d%d", &x, &k);
for (int j = 0; j < LOG; j++)
if (k & (1 << j))
x = up[x][j];
printf("%d\n", x);
}
}
CSES Planets Queries II
Problem. \(Q\) queries of the form \((a, b)\): find the minimum number of teleportations to get from planet \(a\) to planet \(b\), or report that it's impossible.
This is harder. Three cases:
- \(a\) and \(b\) are on the same tail, with \(a\) before \(b\). Answer = distance along the tail.
- \(a\) is on a tail and \(b\) is on the cycle that \(a\)'s tail feeds into. Answer = tail distance to cycle + distance around cycle.
- Otherwise: impossible (\(b\) is on a different component, or \(b\) is on the tail before \(a\)).
Implementation outline:
- Find all cycles. For each node, record whether it's on a cycle and which cycle.
- For each node, compute its distance to the cycle.
- For tail nodes, find which cycle node they first reach.
- For queries on the same cycle, compute distance using cycle length.
We'll use binary lifting for step (4): jump from \(a\) forward and check if we reach \(b\).
Gotcha. If \(a\) is on a cycle and \(b\) is on a tail, the answer is always -1. You can only move forward in a functional graph, and once you're on a cycle you stay on the cycle.
When Binary Lifting Meets Trees
Binary lifting for successor queries is the functional-graph analogue of binary lifting for LCA in trees. In trees, \(\text{up}[x][j]\) is the \(2^j\)-th ancestor. In functional graphs, it's the \(2^j\)-th successor. Same table, same query decomposition, different graph.
If you've already learned LCA with binary lifting, this lesson is pure pattern matching.
Complexity
| Operation | Time | Space |
|---|---|---|
| Precomputation | \(O(N \log K)\) | \(O(N \log K)\) |
| Single query | \(O(\log K)\) | -- |
For \(N = 2 \times 10^5\) and \(K = 10^9\): \(\log K = 30\). Precomputation = \(6 \times 10^6\) operations. Each query = 30 operations. Both trivially fast.
Problems
| Problem | Link | Difficulty |
|---|---|---|
| CSES Planets Queries I | cses.fi/problemset/task/1750 | Medium |
| CSES Planets Queries II | cses.fi/problemset/task/1160 | Hard |