Найти количество пар элементов
$a$ и$b$ в отсортированном массиве, такие что$b - a > K$ .
Наивное решение: бинарный поиск. Будем считать, что массив уже отсортирован. Для каждого элемента
А можно ли быстрее?
Да, давайте перебирать два указателя — два индекса
int second = 0, ans = 0;
for (int first = 0; first < n; ++first) {
while (second != n && a[second] - a[first] <= r) {
second++;
}
ans += n - second;
}
За сколько же работает это решение? С виду может показаться, что за
Это называется метод двух указателей — так как мы двигаем два указателя first и second одновременно слева направо по каким-то правилам. Обычно его используют на одном отсортированном массиве.
Давайте разберем еще примеры.
Найти в отсортированном массиве два числа
$a$ и$b$ такие, что$b - a \leq K$ , и при этом$b-a$ максимально.
Давайте просто переберем
Заметим, что таким образом мы перебрем все пары
Чаще всего метод двух указателей применяют к одному отсортированному массиву. Но иногда можно применить его и на несколько, например, три массива.
Найти в трех отсортированных массивах элементы
$a_i$ ,$b_j$ и$c_k$ такие, что$|\max(a_i, b_j, c_k) - \min(a_i, b_j, c_k)|$ минимально.
Для этого достаточно перебирать указатель
Изначально все указатели указывают на индекс
Так что мы просто сдвигаем каждый раз указатель на минимум из трех элементов, если есть куда его двигать, иначе заканчиваем. Каждый раз обновляем ответ. Так мы переберем всех разумные тройки элементов.
Еще пример двух указателей на нескольких массивах.
Пусть у нас есть два отсортированных по неубыванию массива размера
Пусть первый указатель будет указывать на начало первого массива, а второй, соответственно, на начало второго. Из двух текущих элементов, на которые указывают указатели, выберем наименьший и положим на соответствующую позицию в новом массиве, после чего сдвинем указатель. Продолжим этот процесс пока в обоих массивах не закончатся элементы. Тогда код будет выглядеть следующим образом:
int a[n + 1], b[m + 1], res[n + m];
a[n] = INF; // Создаем в конце массива фиктивный элемент, который заведомо больше остальных
b[m] = INF; // Чтобы избежать лишних случаев
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
for (int j = 0; j < m; ++j) {
cin >> a[j];
}
int i = 0, j = 0;
for (int k = 0; k < n + m; ++k) {
if (a[i] < b[j]) {
res[k] = a[i];
i++;
} else {
res[k] = b[j];
j++;
}
}
Итоговая асимптотика:
Давайте подробно опишем как использовать операцию слияния для сортировки за
Пусть у нас есть какой-то массив.
int a[8] = {7, 2, 5, 6, 1, 3, 4, 8};
Сделаем такое предположение. Пусть мы уже умеем как-то сортировать массив размера
// (7 2 5 6 1 3 4 8)
// (7 2 5 6) (1 3 4 8)
// (7 2) (5 6) (1 3) (4 8)
// (2 7) (5 6) (1 3) (4 8)
// (2 5 6 7) (1 3 4 8)
// (1 2 3 4 5 6 7 8)
#include <algorithm> // Воспользуемся встроенной функцией merge
void merge_sort(vector<int> &v, int l, int r) { // v - вектор, который хотим отсортировать
if (r - l == 1) { // l и r - полуинтервал, который хотим отсортировать
return;
}
int mid = (l + r) / 2;
merge_sort(v, l, mid);
merge_sort(v, mid, r);
vector<int> temp(r - l); // временный вектор
merge(v.begin() + l, v.begin() + mid, v.begin() + mid, v.begin() + r, c.begin());
for (int i = 0; i < r - l; ++i) {
v[i + l] = temp[i];
}
return;
}
Так сколько же работает это решение?
Пускай
Пусть у нас есть некоторая перестановка
Найти количество инверсий в данной перестановке.
Очевидно, что эта задача легко решается обычным перебором двух индексов за
int a[n], ans = 0;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (a[i] > a[j]) {
ans++;
}
}
}
cout << ans << endl;
Внезапно эту задачу можно решить используя сортировку слиянием, слегка модифицируя её. Оставим ту же идею. Пусть мы умеем находить количество инверсий в массиве размера
Заметим, что мы уже знаем количество инверсий в левой половине и в правой половине массива. Осталось лишь посчитать число инверсий, где одно число лежит в левой половине, а второе в правой половине. Как же их посчитать?
Давайте подробнее рассмотрим операцию merge левой и правой половины (которую мы ранее заменили на вызов встроенной функции merge). Первый указатель указывает на элемент левой половины, второй указатель указывает на элемент второй половины, мы смотрим на минимум из них и этот указатель вдигаем вправо.
Рассмотрим число
Значит в тот момент, когда мы добавляем число
Решите как можно больше задач из теоретического контеста https://informatics.msk.ru/mod/statements/view3.php?id=38944
Решите как можно больше задач практического контеста https://codeforces.com/group/g92L0id9Yb/contest/236738