NeFut Logo NeFut
Admin Login

Breaking Time Complexity Bottlenecks: Discretization and Hashing Strategies for Competitive Programming

Published at: 2026-05-29 01:15 Last updated: 2026-06-18 13:58
#algorithm #Data Structure #Discretization

Core Logic and Mathematical Principles

Space-Time Trade-off is the most direct approach to breaking through time complexity bottlenecks. Its underlying mathematical principle is based on the constant-time addressing capability of mapping functions.

The essence of naive searching is traversing through the state space, with time complexity typically of $O(N)$ or $O(N^2)$. By constructing a mapping function $f(x) \to \text{Address}$, data domains can be directly mapped to physical memory addresses, reducing the time complexity of searching, deduplication, and frequency counting to $O(1)$.

When the original data value domain $\mathbb{U}$ is extremely large (e.g., $\mathbb{U} \in [-10^9, 10^9]$) and sparse, directly allocating arrays will cause memory limit exceeded (MLE). In such cases, discretization or hashing must be employed to perform order-preserving or non-order-preserving injective mappings, compressing the sparse large value domain into a compact linear space $[1, N]$, thereby achieving $O(1)$ addressing via arrays without destroying relative ordering or uniqueness.

When the original data value domain $\mathbb{U}$ is extremely large and sparse, directly allocating arrays will cause memory limit exceeded (MLE). In such cases, different spatial mapping strategies must be adopted based on the algorithm's requirements for element ordering:


State Design and Algorithm Derivation

1. Coordinate Discretization (Order-Preserving Mapping)

Let the original sequence be $A = \{a_1, a_2, \dots, a_n\}$ with an extremely large value domain. The core of discretization is to construct a strictly monotonically increasing benchmark sequence $B$.

$$f(a_i) = \text{idx}, \quad \text{where } B[\text{idx}-1] = a_i \text{ (1-based index)}$$

Scenarios where 1-based indexing is preferred: Fenwick trees, prefix sums, segment trees

This process preserves the spatial ordering relationship: if $a_i < a_j$, then $f(a_i) < f(a_j)$. Sorting complexity is $O(N \log N)$, and single conversion complexity is $O(\log N)$.

2. Static Hashing (Non-Order-Preserving Scattering)

For scenarios that do not require maintaining ordering relationships and only pursue pure $O(1)$ access (such as large integer deduplication and frequency counting), directly adopt a static array to simulate a chained forward-star hash table (chaining method). Let the hash function be $H(x) = (x \bmod P + P) \bmod P$, where $P$ is a large prime number. The double modulo here is to prevent negative subscripts. State storage structure:

$$head[H(x)] \to nxt[i] \to nxt[j] \dots$$

By pre-allocating memory through static arrays, the risk of unordered_map degrading to $O(N)$ due to hash collisions in the Linux environment is eliminated.


Algorithm Template

Using static discretization and prefix sum preprocessing to efficiently solve range coverage and discrete frequency counting problems.

#include <iostream>
#include <algorithm>

using namespace std;

const int MAXM = 200005; 
const int MAXN = MAXM * 2 + 5; // After deduplication, at most 2*M points; strictly allocate 2x space to prevent RE

int l[MAXM], r[MAXM];          // Store original query intervals
int raw[MAXN], tot;            // Discretization raw value array and pointer (1-based)
int s[MAXN];                   // Global difference array

int main() {
    // Extreme I/O optimization
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

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

    // 1. Read intervals and flatten into discretization array
    for (int i = 1; i <= m; ++i) {
        cin >> l[i] >> r[i];
        raw[++tot] = l[i];
        raw[++tot] = r[i];
    }

    // 2. Static discretization preprocessing (sorting and deduplication)
    sort(raw + 1, raw + tot + 1);
    tot = unique(raw + 1, raw + tot + 1) - (raw + 1);

    // 3. Core mapping and difference marking
    // Geometric line segment union is treated as left-closed right-open [l, r), standard difference: s[l]++, s[r]--
    for (int i = 1; i <= m; ++i) {
        int disc_l = lower_bound(raw + 1, raw + tot + 1, l[i]) - raw;
        int disc_r = lower_bound(raw + 1, raw + tot + 1, r[i]) - raw;
        s[disc_l] += 1;
        s[disc_r] -= 1;
    }

    long long total_length = 0; // Physical coordinate differences can be extremely large; must use long long
    int current_coverage = 0;

    // 4. Prefix sum scan to count segment lengths
    // The physical segment between discretized points i and i+1 is [raw[i], raw[i+1])
    for (int i = 1; i < tot; ++i) {
        current_coverage += s[i];
        if (current_coverage > 0) {
            total_length += (long long)raw[i + 1] - raw[i];
        }
    }

    cout << total_length << "\n";

    return 0;
}

From the physical reality of input data, problems typically provide closed intervals [L, R]; however, in the algorithm's logic, they are treated as left-closed right-open intervals [L, R).

This is because we are solving geometric line segment length problems, not discrete integer point counting problems. This logic can be thoroughly clarified from two dimensions:

First, why can geometric line segments be directly treated as left-closed right-open? On the number line, a continuous line segment from L to R has a geometric length of R - L. In mathematical geometry, an isolated point has a length of 0. Therefore, the length of the closed interval [L, R], the open interval (L, R), and the left-closed right-open interval [L, R) are completely equal in geometric topology, all being R - L. Since the lengths are equal, we proactively choose the left-closed right-open model for the most convenient and error-free implementation.

Second, the significant advantage of using [L, R) in the algorithm:

s[disc_l] += 1;
s[disc_r] -= 1;

Combined with the subsequent scan loop:

for (int i = 1; i < tot; ++i) {
    current_coverage += s[i];
    if (current_coverage > 0) {
        total_length += (long long)raw[i + 1] - raw[i];
    }
}

This avoids boundary misalignment (±1 traps).

Two Mapping Models for Range Coverage

When handling range coverage, it is essential to distinguish between "point coverage" and "segment coverage," as confusing them can easily lead to fundamental errors.


NOIP Practical Pitfall Guide

1. unordered_map Performance Degradation and Hacker-Constructed Data Collisions

Many contestants place blind faith in the average $O(1)$ complexity of unordered_map. Problem setters can easily force your hash table to degrade to $O(N)$ through specific prime collisions (Anti-Hash Test Data), leading to TLE. In the GCC compiler (C++ standard library) used in NOIP, the internal implementation of hash tables is very straightforward in pursuit of speed, yet it has two fatal fixed patterns:

Integer hash function equals the original value: If you store an integer x, GCC's default hash function performs no obfuscation and directly returns x itself.

The number of buckets is a fixed prime: The hash table internally determines which bucket to allocate data to using hash_value % total_buckets. To reduce collisions, GCC's source code hardcodes a fixed prime table (e.g., 126271, 1000003, etc.). As more data is inserted and the hash table resizes, the number of buckets strictly follows the next prime in this table.

Solution: For non-order-preserving mapping over large value domains, either implement a custom chaining hash table or introduce a custom hash function custom_hash using high-precision timestamps (chrono) as random seeds to perturb hash bucket distribution.

2. Discretization Deduplication Boundaries and Space Doubling

Discretization is typically accompanied by range operations. If each interval has two endpoints $L$ and $R$, the effective size of the discretization array can reach up to $2M$. Contestants who habitually allocate space with $N$ as the array bound will directly encounter runtime errors (RE).

Solution: When defining global static arrays, space must be allocated based on the actual maximum upper bound of discretized elements (typically $2 \times \text{Query\_Size}$), with at least 5 extra units reserved as a safety margin to prevent out-of-bounds from operations like r + 1.


Classic NOIP/Luogu Problems

1. Luogu P1496 The Battle of Red Cliffs

2. Luogu P1908 Inversion Count


[h] Back to Home