NeFut Logo NeFut
Admin Login

Binary Lifting: Mathematical Principles and Implementation for Efficient Interval Queries and Dynamic Coverage

Published at: 2026-05-27 09:17 Last updated: 2026-06-06 13:04
#C++ #Tutorial

Core Logic and Mathematical Principles

Exponential Jump of Binary Lifting

The essence of Binary Lifting lies in transforming the linear search step size into an exponential leap of binary weights. Let the target number of steps be $K$, and any positive integer $K$ can be uniquely decomposed into binary form:

$$K = \sum_{i=0}^{\lfloor \log_2 K \rfloor} c_i \cdot 2^i \quad (c_i \in \{0, 1\})$$

By preprocessing the state transition results for step sizes of $2^i$, any jump of distance can be completed in $O(\log K)$ time complexity by piecing together the binary bits. This eliminates the $O(K)$ step-by-step traversal, forcefully constraining the time complexity to logarithmic levels.

Idempotent Coverage for Range Maximum Queries

In the Range Maximum Query (RMQ) problem, the mathematical foundation of Binary Lifting derives from the idempotency of operators, i.e., $f(x, x) = x$. For extreme value operators like $ ext{max}$ or $ ext{min}$, the union of the maximum values of two overlapping sub-intervals is the maximum of the entire set. Let the length of the interval be $L$, and compute $k = \lfloor \log_2 L \rfloor$. Then the interval $[L, R]$ can be completely covered by two segments of length $2^k$:

$$[L, R] = [L, L + 2^k - 1] \cup [R - 2^k + 1, R]$$

Utilizing this geometric covering property, RMQ queries can be completed in $O(1)$ time.


State Design and Algorithm Derivation

State Design for Jump Steps

Define $dp[i][j]$ to represent the endpoint node number or accumulated weight reached by jumping $2^j$ steps from the starting point $i$. Based on the phase division of the state machine, jumping $2^j$ steps is equivalent to first jumping $2^{j-1}$ steps to reach an intermediate node, and then continuing to jump $2^{j-1}$ steps from that intermediate node. The state transition equation is:

$$dp[i][j] = dp[\,dp[i][j-1]\,][j-1]$$

The base state $dp[i][0]$ is initialized by the direct transition relations given by the problem (adjacency list or single-step pointer). The outer loop enumerates the exponent $j$, while the inner loop enumerates the space nodes $i$, strictly following the topological order.

Extreme Application Derivation of RMQ

In complex scenarios (such as circular intervals and dynamic step coverage), combine the pointer jumps of Binary Lifting. To cover a certain interval, abstract the "next farthest right endpoint that can be covered" as a single-step transition $nxt[i]$. Establish the binary lifting table:

$$f[i][j] = f[\,f[i][j-1]\,][j-1]$$

Where $f[i][j]$ indicates the farthest right endpoint reachable from $i$ after consuming $2^j$ intervals. By enumerating $j$ from large to small on the binary lifting table, the minimum number of intervals required to cover the target interval can be found in $O(\log N)$ time.


C++ Standard Source Code

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// Simulating the classic application of NOIP: Combining circular interval binary lifting coverage with static RMQ
struct Segment {
    int l, r, id;
    bool operator<(const Segment& other) const {
        return l < other.l;
    }
};

const int MAX_LOG = 20;

void solve_binary_lifting() {
    int n, m;
    if (!(cin >> n >> m)) return;

    // Consider circular replication or linear expansion
    vector<Segment> segs(n);
    for (int i = 0; i < n; ++i) {
        cin >> segs[i].l >> segs[i].r;
        segs[i].id = i;
    }

    sort(segs.begin(), segs.end());

    // nxt[i][j] indicates the next index of the farthest interval reachable after jumping $2^j$ intervals from the $i^{th}$ interval
    vector<vector<int>> nxt(n + 1, vector<int>(MAX_LOG, n));

    // Greedy two-pointer preprocessing for single-step transition nxt[i][0]
    int cur_right = 0;
    int max_r = 0, best_idx = n;
    for (int i = 0; i < n; ++i) {
        while (cur_right < n && segs[cur_right].l <= segs[i].r) {
            if (segs[cur_right].r > max_r) {
                max_r = segs[cur_right].r;
                best_idx = cur_right;
            }
            cur_right++;
        }
        // Fatal pitfall: If the farthest cannot be extended, must point to a self-locking node (like n or itself) to prevent out-of-bounds or infinite loops
        nxt[i][0] = (max_r > segs[i].r) ? best_idx : n;
    }

    // Fatal pitfall: All power jumps of the sentinel node n must point to itself to complete the self-loop closure in graph theory
    for (int j = 0; j < MAX_LOG; ++j) {
        nxt[n][j] = n;
    }

    // Strictly according to topological order, outer loop enumerates exponent j, inner loop enumerates node i
    for (int j = 1; j < MAX_LOG; ++j) {
        for (int i = 0; i < n; ++i) {
            nxt[i][j] = nxt[nxt[i][j - 1]][j - 1];
        }
    }

    // Simulating a set of queries: Jump from the interval start until its right endpoint can cover target_r 
    int start_node = 0;
    int target_r = m; 
    int ans = 1; // Count the initial interval
    int curr = start_node;

    for (int j = MAX_LOG - 1; j >= 0; --j) {
        // Fatal pitfall: Try jumping from large to small, only execute the jump when the endpoint after the jump still cannot completely cover the target
        if (nxt[curr][j] != n && segs[nxt[curr][j]].r < target_r) {
            ans += (1 << j);
            curr = nxt[curr][j];
        }
    }

    // Finally, one more jump is needed to achieve full coverage
    ans++; 
    cout << ans << "\n";
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    solve_binary_lifting();
    return 0;
}

NOIP Practical Pitfall Guide

  1. Out-of-bounds infinite loop caused by unclosed virtual nodes and self-loops During the establishment of the binary lifting table, the boundaries where further jumps cannot be made (such as reaching the tree root or the end of the sequence) must point to a specific virtual node (usually 0 or N). All states of this virtual node in the binary lifting table must be hard-coded to point to itself, i.e., nxt[virtual_node][j] = virtual_node. If this closure is not processed, the virtual node will read random garbage values from memory, causing the jumping logic to enter an uncontrollable infinite loop or directly trigger a SIGSEGV segmentation fault.
  2. Greedy boundary errors when piecing jumps from large to small When using the binary lifting table to find the minimum number of jumps or other precise values, it is necessary to attempt jumps from large to small (e.g., decrementing from MAX_LOG - 1 to 0). The decision condition is usually if (condition not met) to execute the jump. Contestants often make the mistake of writing the condition as if (condition met). If they jump directly in the direction of meeting the condition, it may lead to "overshooting" due to the large binary lifting step, thus failing to obtain the optimal precise solution.

Classic NOIP/Luogu Problems

Luogu P1084 [NOIP2012 Advanced Group] Epidemic Control

Luogu P4155 [SCOI2015] Flag Plan

Original Source: local://3.4

[h] Back to Home