Загрузил rodzher.zet

Практика по алгоритмам Бинпоиск и сортировка

Реклама
SPb HSE, 1 курс, осень 2019/20
Практика по алгоритмам #5
Бинпоиск и сортировка
3 октября
Собрано 17 октября 2019 г. в 19:14
Содержание
1. Бинпоиск и сортировка
1
2. Разбор задач практики
3
3. Домашнее задание
3.1. Обязательная часть . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2. Дополнительная часть . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
6
7
4. Разбор домашнего задания
4.1. Обязательная часть . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2. Дополнительная часть . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
8
9
Алгоритмы, осень 2019/20
Практика #5. Бинпоиск и сортировка.
Бинпоиск и сортировка
1. Нижняя оценка на почти рабочую сортировку
Покажите, что любая сортировка, которая верно работает хотя бы на доле
становок, работает за Ω(𝑛 log 𝑛).
1
100
от всех пере-
2. Сложность случайных сортировок
Есть много разных сортировок. Есть даже такие, что работают дольше, чем квадрат.
Оцените время работы в среднем следующих сортировок:
while not sorted
a) i = rand(), j = rand(), if a[min(i,j)] > a[max(i,j)] swap
b) (*) i = rand(), j = rand(), swap
3. Почти отсортированный массив
В массиве длины 𝑛 каждый элемент отстоит от своей правильной позиции на 6 𝑘.
a) Отсортируйте за 𝒪(𝑛𝑘).
b) Отсортируйте за 𝒪(𝑛 + 𝐼), где 𝐼 – число инверсий.
c) Отсортируйте за 𝒪(𝑛 log 𝑘).
d) Докажите нижнюю оценку на время сортировки Ω(𝑛 log 𝑘).
4. Anti-QuickSort test
Пусть в качестве разбивающего элемента всегда берется средний элемент: ⌊(𝑙 + 𝑟)/2⌋.
Построить массив длины 𝑛, на котором QuickSort отработает за Ω(𝑛2 ).
5. Ломаная без самопересечений
Дано 𝑛 точек на плоскости. За 𝒪(𝑛 log 𝑛) соединить их
a) (𝑛 − 1)-звенной ломаной без самопересечений (не замкнутой);
b) 𝑛-звенной ломаной без самопересечений (замкнутой).
6. Сканирование отрезков
Дан набор из 𝑛 отрезков [𝑎𝑖 , 𝑏𝑖 ], где 𝑎𝑖 , 𝑏𝑖 – вещественные.
a) Выбрать максимальное число непересекающихся отрезков. 𝒪(𝑛 log 𝑛). Жадно. Доказать!
b) Найти такое вещественное 𝑥, что |{𝑖 : 𝑥 ∈ [𝑎𝑖 , 𝑏𝑖 ]}| максимально.
Условие специально написано так. Тренируйтесь понимать формулы.
c) Для каждого 𝑘 посчитать длину множества точек, покрытых ровно 𝑘 отрезками.
7. Покраска забора
Есть 𝑞 запросов вида color(l,r,c): покраска отрезка [l..r] массива в цвет c.
В конце вывести получившийся массив. Решение в offline за 𝒪(𝑛 + 𝑞 log 𝑞).
(*) Решите ещё быстрее ;-)
8. Коровы – в стойла!
Есть 𝑚 стойл с координатами 𝑥1 , . . . , 𝑥𝑚 и 𝑛 коров. Расставить коров по стойлам так, чтобы
минимальное расстояние между коровами было максимально.
9. K-best
Даны два массива из положительных чисел 𝑎 и 𝑏. |𝑎| = |𝑏| =∑︀𝑛 6 105 .
Выбрать массив 𝑝: 𝑘 различных чисел от 1 до 𝑛 так, чтобы
1/10
𝑘
∑︀𝑖=1
𝑘
𝑎𝑝𝑖
𝑖=1 𝑏𝑝𝑖
→ max.
Алгоритмы, осень 2019/20
Практика #5. Бинпоиск и сортировка.
10. Поиск точки разреза
a) Дан массив 𝑎1 < 𝑎2 < . . . < 𝑎𝑘 > 𝑎𝑘+1 > . . . > 𝑎𝑛 . За 𝒪(log 𝑛) найти 𝑘.
b) Дан массив 𝑎1 6 𝑎2 6 . . . 6 𝑎𝑘 > 𝑎𝑘+1 > . . . > 𝑎𝑛 . За 𝒪(log 𝑛) найти 𝑘.
c) (*) Дан циклический сдвиг на 𝑘 строго возрастающего массива. За 𝒪(log 𝑛) найти 𝑘.
11. (*) Второй максимум
Найти второй максимум в массиве за 𝑛 + 𝒪(log 𝑛) сравнений.
12. (*) Binary search lower bound
Доказать, что «бинпоиск», который умеет делить на структуре данных, которая даёт доступ
к 𝑖-му элементу за Θ(𝑖) и больше ничего не умеет, работает за Ω(𝑛 log 𝑛) в худшем случае.
13. (*) Randomized sort lower bound
Покажите, что не существует такой вероятностной сортировки, которая корректно сортирует массив с вероятностью не менее 21 и имеет cреднее время работы (матожидание) 𝑜(𝑛 log 𝑛).
2/10
Алгоритмы, осень 2019/20
Практика #5. Бинпоиск и сортировка.
Разбор задач практики
1. Нижняя оценка на почти рабочую сортировку
1
1
Чтобы различить 100
𝑛! вариантов ответа, нужно хотя бы log( 100
𝑛!) = Ω(𝑛 log 𝑛) сравнений.
2. Сложность случайных сортировок
Если с вероятностью 𝑝 монетка выпадет орлом, то среднее число подкидываний до первого
орла 𝐸 = 𝑝1 .
Доказательство: 𝐸 = 1+(1−𝑝)𝐸 (один бросок делаем всегда, с вероятностью 1−𝑝 не повезло,
и нужно повторить процесс).
Функцию sorted можно вызывать не каждый раз, а раз в 𝑛 шагов, тогда её амортизированная сложность 𝒪(1).
a) Каждый swap уменьшает число инверсий 𝐼 хотя бы на один. 𝑇 (𝐼) – время работы, если
сейчас 𝐼 инверсий.
𝐼
⇒ среднее число проб до попадания в инВероятность попасть в инверсию 𝑝 = 𝑛(𝑛−1)/2
версию 𝑛(𝑛−1)/2
.
Inv
𝑛(𝑛−1)/2
𝑛(𝑛−1) ∑︀𝐼
1
2
𝑇 (𝐼) 6
+
𝑇
(𝐼
−
1)
=
𝑗=1 𝑗 = 𝒪(𝑛 log 𝑛).
𝐼
2
Забавные факты. Более точный анализ даст оценку Θ(𝑛2 ). Если, когда инверсий осталось ≈ 𝑛 log2 𝑛, переключиться на сортировку вставками, время будет Θ(𝑛 log2 𝑛).
b) На каждом шаге перестановка близка к случайной ⇒ с вероятностью 𝑛!1 отсортирована
⇒ среднее время работы Θ(𝑛!).
3. Почти отсортированный массив
a) За 𝒪(𝑛𝑘). Cортировка вставками. 𝑘 итераций пузырька. Сортировка выбором из 𝑘 ближайших.
b) За 𝒪(𝑛 + 𝐼): сортировка вставками (см. лекцию).
c) За 𝒪(𝑛 log 𝑘).
• Способ #1. Поддерживаем в куче элементы 𝑎[𝑖..𝑖 + 𝑘]. На каждом шаге приписываем
к результату минимум из кучи и кладем в кучу следующий (𝑖 + 𝑘 + 1) элемент.
• Способ #2. Разбиваем массив на кусочки длины 𝑘, сортируем каждый, затем слева
направо сливаем куски: merge(1,2), merge(2,3), merge(3,4), . . .
Корректность: после первого merge все элементы первого куска стоят на своих местах, т.к. исходно находились или в первом, или во втором. Далее по индукции.
d) Нижняя оценка Ω(𝑛 log 𝑘): в 𝑖-м куске длины 𝑘 может быть любая перестановка элементов
𝑖𝑘+1, . . . , 𝑖𝑘+𝑘, на сортировку каждого куска нужно 𝑘 log 𝑘 сравнений, кусков 𝑛/𝑘.
Иными словами: число перестановок, которые нужно уметь отличать, не менее (𝑘!)𝑛/𝑘 .
Получаем число сравнений не менее log(𝑘!)𝑛/𝑘 = 𝑛𝑘 log 𝑘! = Ω(𝑛 log 𝑘).
4. Anti-QuickSort test
Чтобы QSort работал за квадрат, достаточно получить реккурентность 𝑇 (𝑛) = 𝑇 (𝑛 − 1)+𝑛.
a) Если разбиваем по первому элементу, то подойдут {1, 2, . . . , 𝑛} и {𝑛, . . . , 2, 1}.
b) Если разбиваем по среднему, поставим в середину число 𝑛.
Нужно, чтобы после partition получился плохой массив размера 𝑛 − 1.
Пусть partition реализован так: code ⇒
во время partition средний элемент 𝑛 поменяется местами с последним.
Строим рекурсивно массив 𝑎 длины 𝑛 − 1, пишем в конец 𝑛, делаем swap(𝑎[𝑛/2], 𝑎[𝑛 − 1]).
3/10
Алгоритмы, осень 2019/20
Практика #5. Бинпоиск и сортировка.
5. Ломаная без самопересечений
a) Ломаная: sort пар ⟨𝑥, 𝑦⟩, соединим в таком порядке.
b) Замкнутая ломаная: вокруг любой из данных точек sort по углу, при равенстве по возрастанию расстояния. Точки с самым большим углом нужно сортировать по убыванию
расстояния.
6. Сканирование отрезков
a) Непересекающиеся отрезки. Отрезок = [𝐿𝑖 , 𝑅𝑖 ]. Сортируем по возрастанию 𝑅𝑖 , жадно
берём отрезки. Проверка, что можно взять: 𝐿𝑖 > 𝑅last .
Корректность: рассмотрим любой ответ, пусть в нём нет min по 𝑅𝑖 отрезка. Тогда можно
заменить самый левый на min по 𝑅𝑖 , он заканчивается ещё левее ⇒ не пересечётся с
оставшимися. Итого ∃ оптимальный ответ, содержащий min по 𝑅𝑖 .
Из оставшихся по индукции выгодно снова взять минимальный по 𝑅𝑖 .
b) События. Пары (𝑙𝑖 , open), (𝑟𝑖 , close) назовём событиями, сортируем их (open < close).
Сканируем все события слева направо, поддерживая число открытых отрезков. Находясь
в точке 𝑥, мы прошли все события левее 𝑥 ⇒ знаем, сколько отрезков накрывает 𝑥.
При целых координатах часто удобно делать (𝑙𝑖 , open), (𝑟𝑖 + 1, close).
c) То же самое. В прошлом пункте обозначим за 𝑥𝑖 координаты событий. Тогда весь промежток (𝑥𝑖 , 𝑥𝑖 +1) покрыт одинаковым числом отрезков 𝑘𝑖 . ans[k𝑖 ] += x𝑖+1 -x𝑖 .
7. Покраска забора
∙ Если мы владеем деревом отрезков с присваиванием на отрезке, то можно напрямую применить его к этой задаче. 𝒪(𝑛 + 𝑞 log 𝑛). Если не владеем, это прекрасно, задачу можно
решить проще!
∙ Идем слева направо, обрабатывем события «начало отрезка» и «конец отрезка». Храним
открытые отрезки в куче (set), сортирующей их по времени покраски.
Каждую точку красим в цвет открытого отрезка с самым поздним временем.
Для каждой точки цвет узнаем за 𝒪(1) (посмотреть минимум), каждое начало и конец
отрезка – положить или вынуть из кучи за 𝒪(log 𝑞).
∙ Будем выполнять запросы с конца. Выполнить запрос = покрасить все ещё не покрашенные клетки на отрезке. Пусть у каждой клетки 𝑖 есть цвет 𝑐𝑖 и указатель на ближайшую
справа непокрашенную 𝑝𝑖 . Тогда
int paint( int l, int r, int color ) {
if (l > r) return l;
if (c[l] == -1) c[l] = color;
return p[l] = paint(p[l], r, color);
}
paint возвращает клетку, до которой докрасил.
Заметим сходство этого кода с СНМ со сжатием путей. В таком виде он работает за
𝒪((𝑛+𝑞) log 𝑛). Если добавить ранговую эвристику, будет 𝒪((𝑛+𝑞)𝛼(𝑛)).
8. Коровы – в стойла!
Бинпоиск по ответу. Проверка, что можно выбрать 𝑚 стойл на расстоянии > 𝑑: самое левое
берём, от него самое левое на расстояние хотя бы 𝑑 и т.д.
4/10
Алгоритмы, осень 2019/20
Практика #5. Бинпоиск и сортировка.
9. K-best
∑︀𝑘
∑︀𝑖=1
𝑘
𝑎𝑝𝑖
𝑖=1 𝑏𝑝𝑖
→ max. Бинпоиск по ответу:
∑︀𝑘
∑︀𝑖=1
𝑘
𝑎𝑝𝑖
𝑖=1 𝑏𝑝𝑖
>𝑀 ⇔
𝑘
∑︀
(𝑎𝑝𝑖 − 𝑀 𝑏𝑝𝑖 ) > 0,
𝑖=1
наличие таких можно проверить, взяв 𝑘 индексов с максимальными 𝑎𝑗 − 𝑀 𝑏𝑗 .
Чтобы взять 𝑘 максимумов за 𝒪(𝑛), выберем (𝑛 − 𝑘)-ю статистику и вернём все элементы
после неё. Сложность 𝒪(𝑛 log MAX).
10. Поиск точки разреза
a) Предикат «𝑎𝑖 > 𝑎𝑖−1 » сперва TRUE, затем FALSE. Бинпоиском ищем границу.
b) Рассмотрим массив {1, 1, . . . , 1, 1, 2, 1, 1, . . . , 1, 1}. Мы ищем число 2, оно может быть на
любой позиции. Если мы посмотрели не все ячейки, то число 2 может оказаться там, где
не смотрели ⇒ в худшем случае нужно просмотреть все 𝑛 ячеек.
c) Предикат «𝑎𝑖 > 𝑎0 » сперва TRUE, затем FALSE. Бинпоиском ищем границу.
11. (*) Второй максимум
Рандомизированное решение. Сделаем random_shuffle массива, который делает 0 сравнений и работает за 𝒪(𝑛). Теперь:
int m1 = a[1], m2 = INT_MIN;
for (int i = 2; i <= n; i++)
if (a[i] > m2)
if (a[i] > m1) m2 = m1, m1 = a[i];
else m2 = a[i];
На 𝑖-й ∑︀
итерации цикла мы войдём в if с вероятностью 2𝑖 , поэтому число сравнений равно
𝑛−1 + 𝑛𝑖=2 2𝑖 = 𝑛 + 2 ln 𝑛 + Θ(1).
Детерминированное решение. Воспользуемся кучей.
Построить кучу мы за 𝑛 сравнений не сможем, зато можем толкнуть каждый элемент вверх.
for (int i = n; i > 1; i++) if (a[i / 2] < a[i]) swap(a[i / 2], a[i]);
В результате максимум всплывёт вверх. Где искать второй максимум? Он не всплыл до
корня, потому что где-то по дороге встретился с первым максимумом ⇒ он в детях вершин,
по которым прошел максимум, всего log2 𝑛 кандидатов.
12. (*) Binary search lower bound
Пусть искомый элемент находится в последних 𝑛2 элементах. Тогда, чтобы его найти нам
нужно сделать хотя бы log 𝑛2 обращений к элементам, чтобы различить 𝑛2 возможных ответов.
Каждое обращение работает хотя бы за 𝑛2 . Итого Ω(𝑛 log 𝑛).
13. (*) Randomized sort lower bound
Есть ещё более сложная задача: дать оценку Ω(𝑛 log 𝑛) на число сравнений. Нам же нужно
оценить лишь время работы, поэтому число случайных бит 𝑟, которые мы просмотрим, то же
должно быть 𝑜(𝑛 log 𝑛). Итого 𝑘 сравнений + 𝑟 случайных бит дают 2𝑟+𝑘 различных исходов
⇒ для корректной сортировки 2𝑟+𝑘 > 𝑛! ⇒ 𝑟 + 𝑘 = Ω(𝑛 log 𝑛).
5/10
Алгоритмы, осень 2019/20
Практика #5. Бинпоиск и сортировка.
Домашнее задание
3.1. Обязательная часть
1. (3) Сложность сортировок
a) (1) Покажите, что любая сортировка, которая верно работает хотя бы на доле 1001 𝑛 от
всех перестановок, не может работать за 𝑜(𝑛 log 𝑛) на всех тестах.
b) (1) Покажите, что нельзя реализовать структуру данных, которая умеет то же, что и
куча: Add, и ExtractMin, но за 𝑜(log 𝑛) сравнений.
c) (1) Покажите, что нельзя сделать merge(a, a+n/2, a+n) за 𝑜(𝑛) сравнений.
merge сливает левую и правую половины, результат кладёт в [a, a+n))
2. (2) Anti-QuickSort test
Построить за 𝒪(𝑛2 ) перестановку длины 𝑛, на котором QuickSort отработает за Ω(𝑛2 ), если
разбивающего элемента берется следующим образом.
Есть некоторая произвольная детерминированная функция pivot(l, r), она возвращает
число от l до r, x = a[pivot(l, r)].
Есть произвольная детерминированая partition(l, r, x). То, как она переставит элементы на отрезке [l, r], завиcит только от результатов сравнения элементов с x. Естественно,
она делает partition – сначала ставит элементы < x, потом x, потом > x.
Алгоритм построения плохой перестановки имеет право вызывать и pivot, и partition.
(*) Можно получить (+1) балл для cледующего случая: pivot(l, r) возвращает три индекса l 6 i, j, k 6 r. За x берется медиана чисел a[i], a[j], a[k]. Дальше то же самое.
3. (5) Отрезки на прямой
Дано 𝑛 отрезков [𝑎𝑖 , 𝑏𝑖 ].
a) (1.5) Найти длину объединения отрезков.
b) (1.5) Выбрать минимальное число отрезков, покрывающих отрезок [0, 𝑀 ]. 𝒪(𝑛 log 𝑛).
В этой задаче важна строгость доказательства.
c) (2) Теперь пусть границы отрезка меняются со временем. 𝑖-й отрезок в момент времени
𝑡 равен [𝑥𝑖 − 𝑡 · 𝑟𝑖 , 𝑥𝑖 + 𝑡 · 𝑟𝑖 ]. Найти первый момент времени, когда отрезки покроют [0, 𝑀 ].
4. (2) Ускорение SiftUp
Модифицируйте операцию SiftUp для бинарной кучи так, чтобы она по-прежнему работала
за 𝒪(log 𝑛), но при этом делала лишь 𝒪(log log 𝑛) сравнений.
5. (2) Ускорение SiftDown
Модифицируйте операцию SiftDown для бинарной кучи так, чтобы она по-прежнему работала за 𝒪(log 𝑛), но при этом делала лишь log2 𝑛 + 𝒪(log log 𝑛) сравнений.
6/10
Алгоритмы, осень 2019/20
Практика #5. Бинпоиск и сортировка.
3.2. Дополнительная часть
1. (2) Различные элементы
Задача: дан массив, проверить, что все элементы различны.
Пусть для работы с элементами массива нам доступна только операция сравнения.
Доказать оценку Ω(𝑛 log 𝑛) на число сравнений.
2. (2) Дополнительные отрезки на прямой
Есть 𝑛 отрезков на прямой. Выбрать из них максимальное по размеру множество, покрывающее каждую точку не более, чем 𝑘 раз. Важна строгость доказательства.
3. (2) Отрезки на окружности
Есть 𝑛 отрезков на окружности. Выбрать из них максимальное по размеру множество, покрывающее каждую точку не более, чем один раз. Оцениваться будут решения за 𝑜(𝑛2 ).
7/10
Алгоритмы, осень 2019/20
Практика #5. Бинпоиск и сортировка.
Разбор домашнего задания
4.1. Обязательная часть
1. Сложность сортировок
𝑛!
𝑛!
a) Надо различать 100
𝑛 вариантов, надо log 100𝑛 = log 𝑛! − 𝑛 log 100 = Ω(𝑛 log 𝑛) сравнений.
b) Если Add и ExtractMin работают за 𝑜(log 𝑛) сравнений, то можно остортировать за
𝑜(𝑛 log 𝑛): 𝑛 раз Add, потом 𝑛 раз ExtractMin.
c) merge(a, a+n/2, a+n) за 𝑜(𝑛) сравнений ⇒ MergeSort за 𝑇 (𝑛) = 2𝑇 ( 𝑛2 )+𝑜(𝑛) = 𝑜(𝑛 log 𝑛).
Более того, можно(︀ показать
нижнюю оценку 𝑛−1 сравнений:
)︀
𝑛−1
𝑛
2
2𝑛−1
вариантов ответа 𝑛/2 > √
⇒ сравнений нужно ⌈log( √
)⌉ = 𝑛−1.
𝑛/2
𝑛/2
2. Anti-QuickSort test
Основная идея: поставим на позицию pivot максимальный элемент, вызовем partition,
который как-то перемешает оставшиеся 𝑛−1 элемент, рекурсивно построим контрпример
для примера длины 𝑛−1.
for (int i = 0; i < n; ++i) b[i] = {0, i};
for (int i = 0; i < n; ++i) {
b[pivot(0, n - i)].first = n - i; // максимум
partition(b, b + n - i, n - i);
}
for (int i = 0; i < n; ++i) a[b[i].second] = b[i].first;
return a;
b[i].second – позиция, на которой i-й элемент стоял изначально, до всех partition.
Когда pivot(l,r) = (i,j,k), делаем a[j] = n-1, a[k] = n. Дальше то же самое.
3. Отрезки на прямой
a) Длина объединения отрезков.
Сортируем события (𝑙𝑖 , open), (𝑟𝑖 , close). Сканируем все события слева направо, поддерживая число открытых отрезков cnt. Суммируем длину части прямой, где cnt > 0.
Можно получить не только длину, но и само объединение: счетчик в точке 𝑥 стал больше
0 ⇒ запомним 𝑥; счетчик в точке 𝑦 стал 0 ⇒ добавляем в ответ отрезок [𝑥, 𝑦].
b) Минимальное число покрывающих отрезков.
Сортируем отрезки по началу. Храним первую непокрытую точку 𝑥, сначала 𝑥 = 0.
Среди отрезков с 𝑙𝑖 6 𝑥 выбираем отрезок с max 𝑟𝑖 , включаем его в ответ и делаем 𝑥 = 𝑟𝑖 .
𝒪(𝑛 log 𝑛) на сортировку, затем проход за 𝒪(𝑛).
Корректность. Пусть в оптимальный ответ не взят отрезок с max 𝑟𝑖 из тех, у кого 𝑙𝑖 6 0.
Выкинем из ответа отрезок 𝑠 с минимальным 𝑟𝑖 , возьмем вместо него наш отрезок 𝑠* .
Отрезок [0, 𝑀 ] по-прежнему покрыт, так как [0, 𝑀 ] ∩ 𝑠 ⊂ 𝑠* ⇒ ∃ оптимальный ответ,
содержащий 𝑠* ⇒ можно жадно взять 𝑠* , остаток ответа построить по индукции.
c) Отрезки [𝑥𝑖 − 𝑡 · 𝑟𝑖 , 𝑥𝑖 + 𝑡 · 𝑟𝑖 ].
Бинпоиск по ответу – времени, когда отрезки покроют [0, 𝑀 ].
Проверка внутри бинпоиска: запускаем предыдущий пункт. Время 𝒪(𝑛 log 𝑛 log MAX).
Более быстрые решения смотрите в доп.части разбора.
8/10
Алгоритмы, осень 2019/20
Практика #5. Бинпоиск и сортировка.
4. Ускорение SiftUp(v)
Записываем в массив значения вершин на пути от нашей 𝑣 до корня, ищем бинпоиском, куда
вставить 𝑣. Поднимаем на нужную высоту за 𝒪(log 𝑛), но уже без сравнений.
Можно делать и с 𝒪(1) доп. памяти: позиция 𝑖 пути – это позиция 𝑣 >> 𝑖 в куче.
5. Ускорение SiftDown(v)
Пусть мин куча. Найдем путь, по которому наша вершина может пойти вниз. Спускаться
нужно всегда в меньшего ребенка. За log2 𝑛 сравнений выписываем в массив значения на
таком пути, за 𝒪(log log 𝑛) сравнений ищем бинпоиском позицию вставки.
Можно делать и с 𝒪(1) доп. памяти: сначала спуститься в самый низ, а затем вызвать SiftUp
из предыдущего пункта.
4.2. Дополнительная часть
3c. Отрезки на прямой
∙ Решение бинпоиском за 𝒪(𝑛(log 𝑛 + log MAX)).
Заранее один раз отсортируем центры отрезков.
Бинпоиск по ответу, внутри бинпоиска перебираем отрезки (центры слева направо) и поддерживаем 𝑟 – самую правую не покрытую точку на [0, 𝑀 ]. Если очередной отрезок 𝑠 покрыл
𝑟, сдвинем её вправо. Если же 𝑠 не покрыл 𝑟, то 𝑠 можно просто пропустить т.к. тот, кто
покроет 𝑟 будет целиком содержать 𝑠.
∙ Решение событиями за 𝒪(𝑛 log 𝑛).
Будем моделировать процесс роста отрезков. Интересные события:
a) Отрезки склеились.
b) Один левый конец обогнал другой левый конец.
c) Один правый конец обогнал другой правый конец.
d) Точки 0 или точка 𝑀 впервые покрылись.
Ответ – максимум по временам событий типа (a), (d).
События типа (d) учтём руками за 𝒪(𝑛).
Остальные события храним в куче, упорядоченной по времени.
Также храним упорядоченные на данный момент времени списки левых и правых концов.
Для каждого помним, к какому отрезку он сейчас относится.
Наконец, храним упорядоченный по координате список непересекающихся отрезков. Для
каждого помним его концы: координаты и скорости.
Алгоритм: пока есть события, обрабатываем самое раннее.
При обработке каждого события меняем в списках то, что нужно изменить, обновляем границы отрезков, обновляем события обгона и склейки.
coreidea При событии обгона про тот конец, который обогнали, забываем навсегда – больше он ни на что не повлияет.
Время работы 𝒪(𝑛 log 𝑛): 3𝑛−3 событий (по 𝑛−1 каждого типа), каждое за 𝒪(log 𝑛).
1. Различные элементы
Рассмотрим работу нашего алгоритма на перестановке. Пусть элемент 𝑥 ни разу не был
сравнен с 𝑥 + 1. Тогда сделаем оба элемента равными 𝑥, алгоритм увидит те же результаты
сравнений, а верный ответ изменился.
⇒ алгоритм должен сравнить каждый 𝑥 с следующим за ним в сортированном порядке
9/10
Алгоритмы, осень 2019/20
Практика #5. Бинпоиск и сортировка.
⇒ по результату сравнений он может отсортировать любую перестановку
⇒ Ω(𝑛 log 𝑛) сравнений в худшем случае.
2. Дополнительные отрезки на прямой
Решение с событиями.
Отсортируем события «отрезок начался», «отрезок закончился». Жадно пытаемся взять все
отрезки. Если уже открытых стало больше чем 𝑘, выкинем отрезок с самым правым 𝑟𝑖 .
Решение сортировкой по 𝑅.
Также как и для 𝑘=1 сортируем в порядке возрастания 𝑅, жадно берём. Поддерживаем set
𝑘 правых концов, приписываем новый R𝑖 к upper_bound(L𝑖 )-1.
3. Отрезки на окружности
http://habrahabr.ru/company/spbau/blog/254093/
Сортируем отрезки по возрастанию 𝑟𝑖 . Почему теперь не повторить жадность для случая на
прямой? Непонятно, как выбрать первый отрезок. Все дальнейние решения основываются
на идее «переберём варианты первого отрезка».
Сначала найдем за 𝒪(𝑛) двумя указателями для каждого отрезка 𝑖 следующий за ним в
жадном порядке next𝑖 = min 𝑗 : 𝑙𝑗 > 𝑟𝑖 .
Заметим, что удобно утроить массивы, чтобы не мучиться с зацикливаением.
Далее решения могут различаться.
Идея за 𝒪(𝑛 log 𝑛).
Насчитаем «двоичные подъёмы» next[𝑖, 𝑘] – переход на 2𝑘 ссылок 𝑖 → next𝑖 вперёд от 𝑖.
Также храним, какую длину мы при этом проходим.
Теперь можно за 𝒪(log 𝑛) для каждого отрезка 𝑖 посчитать, насколько вперед можно перейти
до того, как пройдем 𝑀 – длину круга.
Идея за 𝒪(𝑛).
Возьмём любой первый отрезок. Посчитаем за 𝒪(𝑛) ответ для него – 𝑘 отрезков.
Оптимальный ответ или 𝑘, или 𝑘 + 1: не более одного отрезка накрывает нашу точку разреза
круга.
Правые концы взятых отрезков поделили окружность на 𝑘 частей. Одна из них содержит не
более 𝑛𝑘 правых концов. Попробуем сделать 𝑘 + 1 шагов из каждого.
Другая идея за 𝒪(𝑛).
Насчитаем next𝑖 для 𝑖 = 0..2𝑛. Ссылки next𝑖 задают лес. Наша задача – от каждой вершины
леса подняться на 𝑘 + 1 вверх. Это задача LA, её можно решить за 𝒪(𝑛) одним dfs по лесу
от корней вниз.
10/10
Скачать