Lesson 10 of 12

Ranks change; identities do not

Update ranked sets and optionally preserve duplicate occurrences with IDs.

A rank belongs to the current state

In {5, 12, 20}, 12 has rank 1. Insert 8 and its rank becomes 2. If a command later erases 5, the ranks shift again. Store an actual key or ID to remember an item; a saved rank is not a stable identity.

A dynamic distinct-key command processor can insert(x), erase(x), order_of_key(x), and guarded find_by_order(k). Queries do not create keys. With q commands, a safe bound including input work is O(q(1 + log(q + 1))); storage follows the maximum live set.

Optional extension: duplicate values need distinct full keys

Represent each occurrence as (value, unique_id) in a PBDS tree with std::less<pair<int,int>>. Pair order compares value first, then ID. Equal values remain adjacent, but each occurrence is a different full key.

If IDs are 0 through n − 1, order_of_key({x, -1}) counts occurrences strictly below x, while order_of_key({x, n}) counts those at most x. The sentinels are justified by the ID range. Do not recycle an ID while its old pair remains stored; do not use x + 1 when a pair boundary avoids possible overflow.

C++23
// Complete GNU PBDS extension program in C++23 mode. No input.
#include <functional>
#include <iostream>
#include <utility>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using Item = std::pair<int, int>;
using OrderedBag = __gnu_pbds::tree<
    Item, __gnu_pbds::null_type, std::less<Item>,
    __gnu_pbds::rb_tree_tag,
    __gnu_pbds::tree_order_statistics_node_update>;
int main() {
    OrderedBag values;
    values.insert({5, 0});
    values.insert({2, 1});
    values.insert({5, 2});
    values.insert({8, 3});
    std::cout << values.order_of_key({5, -1}) << '\n';
    std::cout << values.order_of_key({5, 4}) << '\n';
    auto ranked = values.find_by_order(2);
    if (ranked != values.end()) std::cout << ranked->first << '\n';
    auto one = values.lower_bound({5, -1});
    if (one != values.end() && one->first == 5) {
        Item key = *one;
        values.erase(key);
    }
    std::cout << values.size() << '\n';
}

Erase the occurrence you actually found

To remove one 5, lower_bound({5, -1}) locates the first possible 5. Check both end() and the value field, copy the full pair key, then erase that key. A bound may point to the next larger value when 5 is absent.

The program prints 1, 3, 5, 3: one value is below 5, three are at most 5, rank 2 has value 5, and one occurrence is removed. Strict ordering remains valid throughout.

Pause & predict

Pairs (4,0), (4,1), (9,2) are stored. Which claim is true?

Choose an answer
Try it yourself

Turn the idea into code

A ranked distinct set starts as {5, 12, 20}. Insert 8, erase 5, then ask for rank 1 and the number of keys below 12. Explain why an earlier saved rank may be misleading.

Give me a hint

Rebuild the sorted state after every update.

Show the explained solution

The states are {5, 8, 12, 20}, then {8, 12, 20}. Rank 1 is now 12, and one key is below 12. An earlier rank can identify a different item after updates; retain the key when identity matters.

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.