NeFut Logo NeFut
Admin Login

Efficient Algorithms: Solving Inversion Pairs and Modular Arithmetic with Merge Sort and Fast Exponentiation

Published at: 2026-05-29 00:44 Last updated: 2026-06-06 13:04
#C++ #Tutorial

Core Logic and Mathematical Principles

Counting Inversion Pairs with Merge Sort

An inversion pair is defined as a pair $(i, j)$ satisfying $i < j$ and $A[i] > A[j]$. Using the divide-and-conquer approach, the sequence $A[l..r]$ is divided into $A[l..mid]$ and $A[mid+1..r]$. During the merge step with two pointers, if the element pointed to by the left sequence pointer $p_1$ is greater than the element pointed to by the right sequence pointer $p_2$, since the left sequence is already sorted, all elements in $A[p_1..mid]$ are greater than $A[p_2]$. At this point, the contribution to the count of inversion pairs across the two parts is:

$$ \Delta = mid - p_1 + 1 $$

The overall time complexity is $O(N \log N)$, and the space complexity is $O(N)$.

Fast Exponentiation Using Bit Manipulation

To compute $a^b \pmod m$, if the linear recursive complexity is $O(b)$, it will definitely lead to TLE when $b \le 10^{18}$. By using binary decomposition, $b$ can be expressed as:

$$ b = \sum_{i=0}^{k} c_i \cdot 2^i \quad (c_i \in {0, 1}) $$

Thus:

$$ a^b = \prod_{i=0}^{k} a^{c_i \cdot 2^i} $$

Using the doubling method, we update $a \leftarrow a^2 \pmod m$ in each iteration. If the current least significant bit of $b$ is $1$ ($b \& 1 = 1$), we multiply the current value into the answer. The time complexity is $O(\log b)$, and the space complexity is $O(1)$.


Algorithm Derivation and State Design

Derivation of Inversion Pairs with Merge Sort

The divide-and-conquer process satisfies the recurrence relation:

$$ T(N) = 2T\left(\frac{N}{2}\right) + O(N) $$

Solving this gives $T(N) = O(N \log N)$. During the merge, we set two pointers $i = l, j = mid + 1$.

  1. If $A[i] \le A[j]$: $A[i]$ is correctly positioned, and no inversion pair is created, so $i \leftarrow i + 1$.
  2. If $A[i] > A[j]$: $A[j]$ is correctly positioned, implying that all elements from $A[i]$ to $mid$ form inversion pairs with $A[j]$. We update the counter as $ans \leftarrow ans + (mid - i + 1)$ and increment $j \leftarrow j + 1$.

Derivation of Fast Exponentiation Using Bit Manipulation

Let $ans = 1 \pmod m$. The loop continues while $b > 0$:

  1. Check the least significant bit: if (b & 1) $\Rightarrow$ $ans = ans \cdot a \pmod m$.
  2. Square the base: $a = a \cdot a \pmod m$.
  3. Right shift the exponent: b >>= 1.

C++ Standard Code

#include <iostream>
#include <vector>

using namespace std;

// Counting inversion pairs with merge sort, using global variables or passing by reference to avoid copying overhead
long long inverse_pairs_count = 0;

void merge_sort(vector<int>& a, vector<int>& temp, int l, int r) {
    if (l >= r) return;

    int mid = l + ((r - l) >> 1); // Prevent overflow, use bit manipulation for speed

    merge_sort(a, temp, l, mid);
    merge_sort(a, temp, mid + 1, r);

    int i = l, j = mid + 1, k = l;
    while (i <= mid && j <= r) {
        if (a[i] <= a[j]) {
            temp[k++] = a[i++];
        } else {
            temp[k++] = a[j++];
            // Critical pitfall: must convert to long long to prevent overflow during summation; accumulate remaining left interval count at once
            inverse_pairs_count += (mid - i + 1); 
        }
    }

    while (i <= mid) temp[k++] = a[i++];
    while (j <= r) temp[k++] = a[j++];

    for (i = l; i <= r; ++i) {
        a[i] = temp[i];
    }
}

// Fast exponentiation using bit manipulation
long long quick_pow(long long a, long long b, long long m) {
    long long ans = 1 % m; // Critical pitfall: if m=1, the answer must be 0, 1%1=0
    a %= m; // Prevent initial a from exceeding m to avoid overflow in subsequent multiplications
    while (b > 0) {
        if (b & 1) {
            ans = (__int128)ans * a % m; // Use __int128 to prevent overflow when multiplying two long long values
        }
        a = (__int128)a * a % m;
        b >>= 1;
    }
    return ans;
}

int main() {
    // Optimize standard input/output stream, disconnect from stdin/stdout synchronization to improve IO efficiency
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n;
    if (!(cin >> n)) return 0;

    vector<int> a(n);
    vector<int> temp(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }

    merge_sort(a, temp, 0, n - 1);
    cout << inverse_pairs_count << "\n";

    return 0;
}

NOIP Practical Pitfalls Guide

  1. Inversion pair counter overflow with int In the extreme case of a sequence of length $N$, the number of inversion pairs can be as high as $\frac{N(N-1)}{2}$. For the NOIP data range $N = 5 \times 10^5$, the maximum number of inversion pairs is about $1.25 \times 10^{11}$, far exceeding the int limit of $2 \times 10^9$. It is essential to use long long to store the counter, or it will overflow and become negative.
  2. Fast exponentiation multiplication overflow with long long and not taking modulus of $1$ When the modulus $m \approx 10^{18}$, multiplying two long long values can reach $10^{36}$, causing overflow in a 64-bit register. In a Linux 64-bit environment, it is necessary to use __int128 for forced type conversion before taking modulus. Additionally, when $b=0$ and $m=1$, directly returning $1$ will lead to incorrect judgment; the initial answer should be written as 1 % m.

Classic NOIP/Luogu Problems

Luogu P1908 Inversion Pairs

Luogu P1226 [Template] Fast Exponentiation

Original Source: local://3.1

[h] Back to Home