Сжатие координат
Значения до 10^9 заменяются на номера от 0 до n. Три строки кода, которые открывают доступ к массивам там, где их не завести.
3 мин
Задача просит завести массив, индексируемый значениями. Значения — до . Массив такого размера не создать.
Но самих значений не больше — сколько дано во входных данных. Значит, важны не сами числа, а только их порядок. Заменим каждое число его номером в отсортированном списке различных — и все алгоритмы, опирающиеся на порядок, продолжат работать.
Три строки
vector<long long> sorted = a;
sort(sorted.begin(), sorted.end());
sorted.resize(unique(sorted.begin(), sorted.end()) - sorted.begin());
for (long long& value : a)
value = lower_bound(sorted.begin(), sorted.end(), value) - sorted.begin();
После этого в a лежат числа от до , где — количество различных значений, и для любых , верно: новое меньше нового ровно тогда, когда меньше было и старое.
Проверено: на двадцати тысячах случайных массивов все попарные сравнения на «меньше» и «равно» сохранились.
Сложность — на сортировку и на бинарные поиски.
Массив sorted стоит сохранить: по нему восстанавливается исходное значение по номеру, а это почти всегда нужно для вывода ответа.
Где это нужно
Сортировка подсчётом на больших значениях. Сама сортировка тут не нужна, а вот массив счётчиков после сжатия заводится свободно.
Дерево отрезков и Фенвика по значениям. «Сколько чисел меньше уже встретилось» — стандартный запрос, требующий массива по значениям. Со сжатием он становится массивом длины .
Отрезки и события. Координаты до , а точек интереса не больше . После сжатия любая задача про «покрытие» решается массивом.
Динамика по значениям. Состояние вида «наибольшая возрастающая подпоследовательность, оканчивающаяся значением » без сжатия не существует, а после — обычный массив.
Варианты
Если дубликаты нужно различать (например, при равных значениях важен исходный порядок), вместо unique сортируют пары «значение, индекс»:
vector<pair<long long, int>> pairs(n);
for (int i = 0; i < n; i++) pairs[i] = {a[i], i};
sort(pairs.begin(), pairs.end());
for (int rank = 0; rank < n; rank++) a[pairs[rank].second] = rank;
Теперь все номера различны, и равные значения получают номера в порядке появления.
Если чисел много, а разных мало, и порядок не важен вовсе, подойдёт unordered_map<long long, int> — но это медленнее из-за константы хеш-таблицы.
Про константу словаря
Отдельно стоит сказать о выборе между сортировкой и map, потому что он влияет на время сильнее, чем кажется.
map — сбалансированное дерево. Каждая операция — логарифм, но с большой константой: узлы разбросаны по памяти, каждый переход по указателю почти наверняка промах кэша.
sort — тоже , но данные лежат подряд, и процессор читает их последовательно. На практике разница доходит до пяти-десяти раз.
Поэтому если решение уже стоит , а внутри лежит map, — это первое место, куда стоит смотреть при превышении времени. Часто достаточно заменить словарь на сжатие координат плюс обычный массив, и решение проходит без изменения асимптотики.
Общее правило: асимптотика решает, пройдёт ли решение в принципе; константа решает, пройдёт ли оно сегодня. Сжатие координат — самый дешёвый способ уменьшить константу, не трогая идею.