НОВОСТИ Алгоритмы на экзамене в ШАД

BDFpromo
Оффлайн

BDFpromo

.
.
Регистрация
23.09.18
Сообщения
12.347
Реакции
176
Репутация
0
Привет! Меня зовут Александр Курилкин, и я веду курс по алгоритмам в «ШАД Helper». В этом посте я разберу несколько задач из вступительных экзаменов прошлых лет, чтобы вы смогли увидеть, что вас ждет, и понять, чему мы сможем вас научить на нашем курсе. Надеюсь, что вы разделяете мою любовь к интересным задачам по алгоритмам и получите искреннее удовольствие от прочтения этого поста! Итак, приступим...

naimkgdsyccbubz6mky9z7q-kxw.png


28.05.2016, №4


Даны
08d9faefbe272bdf8fbb80773542e343.svg
отрезков
f74083f5f61704a9b0b672bedf591e16.svg
. Назовем индексом вложенности отрезка
f74083f5f61704a9b0b672bedf591e16.svg
количество отрезков, которые его содержат. Предложите алгоритм, определяющий, есть ли в наборе отрезок с индексом вложенности, превышающим 1000. Ограничение по времени —
cbae5c05497ff7402dbf72df48a09595.svg
, по дополнительной памяти —
3b37f30f255db9e1e93d63099fa0d62c.svg
.

Решение

Воспользуемся методом, который в среде олимпиадников называется "сканлайн". Его суть в том, что мы сводим задачу к набору каких-то событий, которые обрабатываем в отсортированном порядке, что-то при этом делая, таким образом решая задачу. В данном случае для каждого отрезка создадим события "отрезок открылся", которое происходит в момент времени
122b006916bcacd39b883735fa907bfa.svg
, и "отрезок закрылся", которое происходит в момент времени
efc54bd547b1a0f308a2ae4281e7d6ed.svg
. Отсортируем все события по их моменту времени и начнем обрабатывать их в таком порядке. Когда отрезок открывается, нужно увеличить счетчик открытых отрезков на 1. Когда открылся очередной отрезок, а счетчик открытых отрезков уже был больше или равен 1000, казалось бы, мы уже решили задачу — очередной отрезок содержится в 1000 уже открытых ранее. Но все не так просто — какие-то из открытых ранее отрезков могли закрыться раньше, чем закроется наш текущий отрезок, который мы рассматриваем как кандидат на ответ. Чтобы решить эту проблему, давайте поддерживать отсортированное мультимножество (std::multiset в C++), в котором будем хранить координаты концов открытых отрезков. На самом деле, хранить все концы в нем не обязательно — достаточно лишь 1000 максимальных. Теперь, чтобы проверить, что текущий отрезок и правда подходит нам, нужно проверить, что в мультимножестве *set.begin(), то есть минимальный (среди 1000 максимальных) конец не левее конца текущего отрезка. Если это правда, то мы нашли подходящий отрезок и задача решена! Если нет, то увы, придется продолжать обрабатывать события раньше. Если отрезок закрылся, то нужно уменьшить счетчик открытых отрезков и удалить из мультимножества его конец. Если все события обработаны, а подходящий отрезок не обнаружен, то его и нет!


У пытливого читателя при прочтении мог возникнуть вопрос: почему достаточно хранить лишь 1000 концов? Вдруг может быть такое, что удалится какой-то из этих 1000 максимальных концов, а открыто было больше 1000 отрезков, и придется откуда-то взять конец, чтобы заменить им только что удаленный? На самом деле такого быть не может, ведь если мы удалили какой-то из 1000 максимальных концов, значит, мы закрыли отрезок с таким концом, а до этого закрыли и все отрезки с концами левее нашего, а значит, заменять в мультимножестве удаленный конец нечем.


Вот и все, задача решена. Мы потратили
cbae5c05497ff7402dbf72df48a09595.svg
времени на сортировку событий,
a3615d0c7d9fddb62835444b32d3e2e9.svg
времени на операции с мультимножеством концов отрезков и O(n) на все остальное. Таким образом, время работы:
cbae5c05497ff7402dbf72df48a09595.svg
. Памяти мы уж точно потратили не больше
3b37f30f255db9e1e93d63099fa0d62c.svg
.



25.05.2019, №4


Дан массив вещественных чисел
493c1c008018df9bed4910321f29ff00.svg
. Предложите алгоритм, находящий для каждого элемента
493c1c008018df9bed4910321f29ff00.svg
индекс ближайшего справа элемента, большего его хотя бы в два раза. Если такого элемента нет, то должно возвращаться значение
ffbcad1b1eea934028c9c71e5a45ec2f.svg
. Ограничение по времени
cbae5c05497ff7402dbf72df48a09595.svg
, по дополнительной памяти —
3b37f30f255db9e1e93d63099fa0d62c.svg
.

Решение

Будем идти по массиву справа налево и поддерживать стек пар
cfae7652717e5ea0d21de3f2b7555312.svg
пройденных чисел. Начнем с самого правого элемента (для него ответ
ffbcad1b1eea934028c9c71e5a45ec2f.svg
), добавим в стек
3ce8c2afdf02ec16e11dc1fb2a7bd52d.svg
, пойдем левее. Пусть мы хотим обработать очередное число с индексом
bf83b532cd867d34004f8eded8c5c79a.svg
. Тогда какие числа, пройденные ранее, не представляют для нас никакого интереса в плане определения ближайшего справа в два раза больше как для нашего числа, так и для чисел левее? Это те числа, которые находятся между нашим и ближайшим большим справа. И правда, зачем они нам в будущем, если у нас уже есть число левее, большее них? А вот ближайшее большее справа нам еще может пригодиться. Поэтому давайте удалять из стека верхние элементы, пока их значения меньше или равны нашему. Как только у верхушки стека значение стало больше, пора остановиться и добавить на вершину стека
4bbee02d5c8d10f9e9fcadaaeaf60424.svg
. Хорошо, со стеком мы поработали, но как теперь определить индекс ближайшего справа в два раза большего числа? Заметим, что числа в стеке отсортированы (сверху вниз) по значению и по расстоянию до
bf83b532cd867d34004f8eded8c5c79a.svg
. Тогда достаточно двоичным поиском в стеке (да, для этого потребуется индексация стека, поэтому давайте вместо стека использовать std::vector) найти наименьшее значение, большее или равное
fc3ac132453a72a67470f47aecd712fa.svg
. Если такое нашлось, нужно взять его индекс (мы же храним его в паре), а если не нашлось, то ответ
ffbcad1b1eea934028c9c71e5a45ec2f.svg
.


Хорошо, задачу мы решили, но какова асимптотика алгоритма? Каждый элемент добавится и удалится из стека всего один раз, поэтому на все добавления и удаления в стек мы потратим O(n), а еще придется сделать
08d9faefbe272bdf8fbb80773542e343.svg
двоичных поисков, что займет
cbae5c05497ff7402dbf72df48a09595.svg
, что все еще укладывается в ограничения задачи. Памяти мы, потратили, очевидно, не больше
3b37f30f255db9e1e93d63099fa0d62c.svg
.



10.06.12, №5
В множестве из
08d9faefbe272bdf8fbb80773542e343.svg
человек каждый может знать или не знать другого (если
493c1c008018df9bed4910321f29ff00.svg
знает
20d8caec693d8d8eaf70885e408419f6.svg
, отсюда не следует, что
20d8caec693d8d8eaf70885e408419f6.svg
знает
493c1c008018df9bed4910321f29ff00.svg
). Все знакомства заданы булевой матрицей
64675c284bcf7c94a946b44f2878e495.svg
. В этом множестве может найтись или не найтись знаменитость — человек, который никого не знает, но которого знают все. Предложите алгоритм, который бы находил в множестве знаменитость или говорил, что ее в этом множестве нет. Сложность по времени —
3b37f30f255db9e1e93d63099fa0d62c.svg
, сложность по памяти —
655b805d68b4b00a4e90f64eefbc6f1c.svg
.

Решение

Для определенности пусть
f784292a43b5ac4c91d734a324823f70.svg
, если
bf83b532cd867d34004f8eded8c5c79a.svg
-й человек знает
b828e2475a3a56280b895f35eb250ea2.svg
-го человека, и 0 иначе.


Тогда заметим, что если
9fe45deb5bdc405e3052fd9d1ca475bb.svg
, то
bf83b532cd867d34004f8eded8c5c79a.svg
-й человек точно не может быть знаменитостью, ведь он кого-то знает, а вот
b828e2475a3a56280b895f35eb250ea2.svg
-й может, а если
6bbf7e5b34c8a668693e525bd8143659.svg
, то
b828e2475a3a56280b895f35eb250ea2.svg
-й человек точно не может быть знаменитостью, ведь
bf83b532cd867d34004f8eded8c5c79a.svg
-й его не знает.


Это дает нам следующий алгоритм: пусть изначально первый человек — наш кандидат в знаменитости. Давайте начнем идти по первой строке матрицы вправо, пока не встретим 1, скажем, в столбце
30fb6814eea0091044df0e5de33dfbc2.svg
. Тогда все люди, соответствующие пройденным столбцам (где был записан 0), точно не могут быть знаменитостями, а человек, соответствующий первой строке, тоже не может быть знаменитостью, поскольку он кого-то знает (а именно, человека, соответствующего строке
30fb6814eea0091044df0e5de33dfbc2.svg
). Тогда
30fb6814eea0091044df0e5de33dfbc2.svg
-й человек — наш кандидат в знаменитости, и начнем такой же процесс с клетки матрицы
18d918b33ed1c9479e0fb2edc0ab0812.svg
. Если на пути нам встретится 1, то обновим кандидатата, и так далее, пока мы можем идти вправо. Осталось проверить, действительно ли является ли наш последний кандидат знаменитостью. Для этого проверим, что в столбце, соответствующем ему, все единицы (кроме клетки на главной диагонали), это значит, что его все знают, а в его строке все нули, то есть он никого не знает. Пока мы шли по матрице, мы двигались только вправо, а значит, потратили
3b37f30f255db9e1e93d63099fa0d62c.svg
времени, а во время финальной проверки пробежались по одной строке и по одному столбцу, что также заняло
3b37f30f255db9e1e93d63099fa0d62c.svg
времени. Итого:
3b37f30f255db9e1e93d63099fa0d62c.svg
времени. Также ничего, кроме нескольких переменных, мы не хранили (считаем, что матрица была дана заранее и в расчет памяти не входит), и памяти мы заняли
655b805d68b4b00a4e90f64eefbc6f1c.svg
. Полное решение!



Это были задачи, требующие смекалки и совсем небольшого знания алгоритмов, а теперь немного алгоритмической жести!


2019, онлайн-контест, задача D


Минимальное остовное дерево, часть 2
Ограничение времени: 2 секунды
Ограничение памяти: 256Mb


Вам дан неориентированный взвешенный граф с
08d9faefbe272bdf8fbb80773542e343.svg
вершинами и
e2e33f15a96008ca33579599483c4531.svg
рёбрами. Требуется выполнить
d68cc4926bf74bae8fa3b51ca4a09ec8.svg
запросов вида «уменьшить вес ребра номер
bf83b532cd867d34004f8eded8c5c79a.svg
на 1». После каждого запроса вам нужно вывести вес минимального остовного дерева в графе.


Остовное дерево графа — это подграф, содержащий все вершины графа и являющийся деревом. Остовное дерево называется минимальным, если сумма весов входящих в него рёбер минимально возможная.


Граф связен и не содержит петель и кратных рёбер. Кроме того, гарантируется, что после каждого запроса веса всех рёбер неотрицательны.


Формат ввода


В первой строке входного файла содержатся два целых числа
08d9faefbe272bdf8fbb80773542e343.svg
и
e2e33f15a96008ca33579599483c4531.svg
— количество вершин и рёбер графа соответственно (
0888963ddc1803233ca34570a49d6b05.svg
. В следующих
e2e33f15a96008ca33579599483c4531.svg
строках содержится по три целых числа
784f98db37a499162c1479600e8465ed.svg
,
1c1778187263268cc347012d16da61e2.svg
,
1d0034a09108db7af7cf42ea23a91ecd.svg
. Это значит, что в графе есть ребро веса
1d0034a09108db7af7cf42ea23a91ecd.svg
, соединяющее вершины
784f98db37a499162c1479600e8465ed.svg
и
1c1778187263268cc347012d16da61e2.svg
(
673b728919646423aa8d2cb37845a7b9.svg
).


В следующей строке содержится целое число
d68cc4926bf74bae8fa3b51ca4a09ec8.svg
— количество запросов (
13a12bbbc41acbb604ebbcfa375a720b.svg
). В следующих
d68cc4926bf74bae8fa3b51ca4a09ec8.svg
строках содержится по одному целому числу
d837e65ff5ecf2b43ef1866a85a9bcf6.svg
(
915ec7e25d3c9944f3ce4e77885a43b4.svg
). Это значит, что требуется уменьшить на единицу вес ребра
d837e65ff5ecf2b43ef1866a85a9bcf6.svg
. Рёбра нумеруются с единицы в порядке следования во входном файле.


Формат вывода


Выведите
d68cc4926bf74bae8fa3b51ca4a09ec8.svg
чисел, разделённых пробелами или переводами строки — веса минимального остовного дерева после выполнения каждого запроса.

Решение

Для начала давайте поймем, как решать задачу, если бы ограничения были поменьше или наши руки были посильнее 99% поступающих в ШАД. Для начала построим минимальное остовное дерево.


Пускай приходит запрос "уменьшить ребро между
784f98db37a499162c1479600e8465ed.svg
и
1c1778187263268cc347012d16da61e2.svg
", и новый вес ребра стал
16da507b2fc389688ef0659939dcc647.svg
. Тогда если в дереве на пути между
784f98db37a499162c1479600e8465ed.svg
и
1c1778187263268cc347012d16da61e2.svg
есть ребро весом больше
16da507b2fc389688ef0659939dcc647.svg
(для поиска такого достаточно найти максимальное ребро на пути. В силу условия задачи оно может быть только весом
44c562936a615ceeecfca5c080e31de8.svg
, иначе бы остов не был минимальным, так как на пути в остове между вершинами ребра
784f98db37a499162c1479600e8465ed.svg
и
1c1778187263268cc347012d16da61e2.svg
было ребро веса
800bf6d4f9ea71edfc2906c15df350f3.svg
, а наше ребро было веса
44c562936a615ceeecfca5c080e31de8.svg
, а значит, выгодно заменить бо́льшее ребро на наше), то нужно удалить его из остова и добавить вместо него наше, уменьшив тем самым вес минимального остовного дерева на 1. Это называется . Осталось понять, как находить максимальное ребро на пути, удалять ребро из дерева и соединять два дерева ребром. Это можно делать либо наивно за
3b37f30f255db9e1e93d63099fa0d62c.svg
, что не позволит нам решить задачу, уложившись в ограничения по времени, либо за
21487538a6dbcc06fa5704effedb4282.svg
с помощью структуры данных , написание которой у вас займет больше времени, чем сам контест (и это в лучшем случае). Поэтому будем искать другое оптимальное решение.


Так как веса бывают всего лишь от 1 до 10, давайте поддерживать 10 остовов (они могут быть лесами, не обязательно деревьями). В
16da507b2fc389688ef0659939dcc647.svg
-м остове будут только ребра весом
630c1797d7299ca9b1450e6dd805defa.svg
. Каждый остов построим . Для этого есть две причины: он просто пишется, и СНМы ( ) для каждого остова, оставшиеся после него, нам еще понадобятся в будущем. Кроме того, можно заметить, что при построении таким образом первый остов будет подмножеством второго, второй третьего, и так далее.


Пусть вес ребра
c741de5be49f902186d30bc5f93ed569.svg
уменьшился на 1 и стал
16da507b2fc389688ef0659939dcc647.svg
. Попытаемся добавить его в
16da507b2fc389688ef0659939dcc647.svg
-й остов. Для начала с помощью СНМ проверим, лежат ли в
16da507b2fc389688ef0659939dcc647.svg
-м остове
784f98db37a499162c1479600e8465ed.svg
и
1c1778187263268cc347012d16da61e2.svg
в одной компоненте связности. Если это не так, то на пути между ними есть ребро веса
44c562936a615ceeecfca5c080e31de8.svg
, а значит, можно его выкинуть и заменить на наше. Но делать все это явно не будем, а просто сделаем слияние компонент
784f98db37a499162c1479600e8465ed.svg
и
1c1778187263268cc347012d16da61e2.svg
в СНМе
16da507b2fc389688ef0659939dcc647.svg
-го остова, заодно уменьшив вес минимального остовного дерева на 1. Если же
784f98db37a499162c1479600e8465ed.svg
и
1c1778187263268cc347012d16da61e2.svg
лежат в одной компоненте связности в
16da507b2fc389688ef0659939dcc647.svg
-м остове, то между ними есть путь по ребрам с весом
630c1797d7299ca9b1450e6dd805defa.svg
, и уменьшение веса нашего ребра не принесет никакой пользы.


Задачу решили, пишется не очень сложно, а что там по времени и памяти?
d00cc1e57dbac07b3bf8b28a46f809a9.svg
на запрос и даже если писать совсем не заботясь о времени, то
131f71b365220b55d96d2752eb6de17e.svg
на препроцессинг. В ограничения должно уложиться, Accepted :)
 
Сверху Снизу