Replace values from a huge range with their ranks in the set of values that actually occur. A -sized dimension becomes , which is what makes array-based data structures applicable.

The idiom

vector<long long> vals = a;                       // all values that matter
sort(vals.begin(), vals.end());
vals.erase(unique(vals.begin(), vals.end()), vals.end());
 
auto id = [&](long long x) {
    return lower_bound(vals.begin(), vals.end(), x) - vals.begin();
};
for (auto& x : a) x = id(x);                      // now in [0, m)

, and vals[i] recovers the original value.

Collect every value that will ever be queried, not just the ones in the array — query endpoints, thresholds, and derived values all need to be in vals.

When you need it

SituationWhy
Values up to , need a BIT/segment tree over valuesarray size must be
Counting inversionsBIT indexed by value
Sweep line with up to segment tree over compressed
$k$-th smallest in a rangepersistent segment tree over values
Grid problems with sparse coordinatescompress rows and columns independently
DP indexed by valuethe state space shrinks to
Offline queries with arbitrary keysmap keys to

Compressing intervals — the +1 trap

Endpoints are not enough for intervals

When compressing intervals , the gaps between consecutive values can matter. Compressing only the endpoints merges distinct empty regions.

Standard fixes:

  1. Insert , and for every interval, then work with half-open intervals .
  2. Or store, alongside each compressed index, the length of the real-world span it represents, and weight sums by that length.

The second is what a rectangle-union sweep does: the segment tree is indexed by compressed , but each leaf’s contribution is ys[i+1] - ys[i], not 1.

Alternatives

AlternativeWhen
map<long long, T>few distinct values, code simplicity matters more than speed
Dynamic (sparse) segment treevalues arrive online, cannot be collected first
Persistent segment tree over the value rangeonline queries, implicit nodes
Hash mapno ordering needed
Sort + binary searchexactly the compression above

Dynamic segment trees are the right choice when the queries are online and you genuinely cannot see all coordinates up front. Otherwise compression is faster and uses less memory.

Compressing more than numbers

The technique is “map arbitrary keys to a dense integer range”, so it applies to:

  • strings — sort and rank, or use a map<string,int>;
  • pairs — sort the distinct pairs;
  • graph vertices given by arbitrary labels;
  • time points in an offline sweep;
  • states in a DP over a sparse state space.

Multi-dimensional compression

Compress each axis independently:

compress(xs); compress(ys);
// grid is now m_x by m_y, with m_x, m_y <= 2n

For a rectangle-union area, the compressed grid has cells — fine for , but for larger use a sweep line over one axis and a segment tree over the other, which is .

A worked example: counting inversions

compress(a);                                       // values now in [0, n)
BIT bit(n);
long long inv = 0;
for (int i = n - 1; i >= 0; i--) {
    inv += bit.query(a[i] - 1);                    // already-seen values smaller than a[i]
    bit.update(a[i], 1);
}

Without compression the BIT would need entries; with it, . This pairing — compress, then index a BIT by value — is one of the most reused combinations in competitive programming.

See also: Fenwick Tree · Sweep Line · Persistent Structures