Lesson 11 of 12

Optional: sparse values and rectangle updates

Compress an enormous value domain or extend difference markers to a grid.

Compression preserves order, not distance

Coordinate compression sorts the distinct keys and gives each its zero-based position as a compact ID. For [−10⁹, 7, 7, 10⁹, 50], distinct keys are [−10⁹, 7, 50, 10⁹]. Count occurrences by those IDs, then build a frequency prefix.

To query values in [L, R], use lower_bound(L) and upper_bound(R) on the sorted distinct-key vector. Their positions form a half-open ID interval; subtract its prefix boundaries. Query endpoints do not need to occur as keys. Compression preserves equality and sorted order, but adjacent IDs do not imply original values are one unit apart.

Choose the simplest static representation

Compression with frequencies takes O(n log(n + 1)) preprocessing, O(log(d + 1)) per query, and O(n + d) storage. For occurrence counts alone, sorting all observations while keeping duplicates and subtracting two bound positions is even simpler.

Use compression when other information is attached to distinct keys or a compact indexed domain is needed. A geometric length or area problem must retain original coordinate distances; counting compressed IDs would lose the gaps.

Four corners for a rectangular addition

For an initially zero grid, add delta inside inclusive corners (r1,c1) and (r2,c2). Record +delta at (r1,c1), −delta at (r2+1,c1), −delta at (r1,c2+1), and +delta at (r2+1,c2+1). Two-dimensional prefix reconstruction spreads and cancels those contributions.

Allocate two extra rows and columns for the stopping boundaries, and print only real cells. The batch method costs O(RC + u) time and O(RC) storage. After in-place reconstruction, entries contain values rather than raw markers; do not append new markers to that changed representation. It handles addition, not assignment.

C++23
// Complete C++23 program. Input: rows cols u, then r1 c1 r2 c2 delta.
// Start with a zero grid. Inclusive valid rectangles; all updates first.
#include <iostream>
#include <vector>
int main() {
    int rows, cols, u;
    std::cin >> rows >> cols >> u;
    std::vector<std::vector<long long>> diff(
        rows + 2, std::vector<long long>(cols + 2, 0));
    for (int j = 0; j < u; ++j) {
        int r1, c1, r2, c2;
        long long delta;
        std::cin >> r1 >> c1 >> r2 >> c2 >> delta;
        diff[r1][c1] += delta;
        diff[r2 + 1][c1] -= delta;
        diff[r1][c2 + 1] -= delta;
        diff[r2 + 1][c2 + 1] += delta;
    }
    for (int r = 1; r <= rows; ++r) {
        for (int c = 1; c <= cols; ++c) {
            diff[r][c] += diff[r - 1][c] + diff[r][c - 1]
                        - diff[r - 1][c - 1];
            if (c > 1) std::cout << ' ';
            std::cout << diff[r][c];
        }
        std::cout << '\n';
    }
}
Pause & predict

Compressed keys are [−10⁹, 7, 50, 10⁹]. How many observations from [−10⁹,7,7,10⁹,50] lie in [8,49]?

Choose an answer
Try it yourself

Turn the idea into code

In a 2×3 zero grid, add 4 to rows 1..2, columns 1..2, then add −1 to row 2, columns 2..3. Write the final grid.

Give me a hint

The overlap at row 2, column 2 receives both additions.

Show the explained solution

The first row is [4, 4, 0] and the second is [4, 3, −1]. The four-corner method combines each update’s contribution and reconstructs exactly these cell values.

Your notes

Ready for the next idea?

Mark complete when you can explain it yourself.

From a problem to a programIdentify the input, output, and constraints, then translate a small plan into C++.Variables, types, and remembered valuesChoose a representation, initialize it, and understand what assignment really changes.Arithmetic without surprisesPredict division and remainder, widen before multiplication, and format decimal answers.Conditions and logical decisionsBuild exclusive categories, combine comparisons, and protect operations with short-circuit logic.Optional: other ways to chooseRecognize switch for exact choices and the conditional operator for choosing a value.For loops: make repetition preciseTrace initialization, condition, body, and update—and avoid an extra iteration.While, do-while, and stopping rulesChoose a loop from its stopping condition and read the exact input format.Break, continue, and early answersDistinguish leaving a loop from skipping one iteration, without losing required input.Five patterns worth understandingUse sums, counts, best-so-far candidates, products, and flags to process a stream.Nested loops: rows and columnsTranslate a shape into two loop roles and count the total work.Take a number apart, one digit at a timeUse remainder and division to process decimal digits, including zero.Multiple cases and a debugging mindsetCombine the patterns, isolate each case, and test the boundaries that can break your idea.Arrays: give every value a placeUnderstand indices, initialization, capacity, active length, and the first safe array loop.Scan, count, search, and compareDistinguish values from positions and preserve the exact tie rule a problem asks for.Change a sequence without losing itUpdate, copy, compare, reverse, and inspect neighbours using the correct bounds.Matrices: think in two coordinatesTraverse tables, total rows and columns, inspect diagonals, and protect grid neighbours.Characters become stringsSeparate characters from numbers, then safely inspect and edit managed text.Read the text the problem actually givesChoose words or full lines, preserve empty cases, and reset only the state that needs it.Scan text: count, filter, transformBuild a result from matching characters without losing leading zeros or skipping shifted text.Join, compare, and find textLearn content comparison, lexicographical order, substring counts, and the not-found sentinel.