Отборочный раунд
Задача A. Две башни.
Идея: Федор Царев.
Реализация: Павел Кротков, Андрей Станкевич.
Разбор: Павел Кротков.
Эта задача является несложным частным случаем задачи о поиске кратчайшего пути в графе. Один из способов решить эту задачу — просто реализовать алгоритм Дейкстры на описанном в условии графе. Однако, из-за большого возможного количества вершин не любая реализация этого алгоритма уложится в ограничение на время работы программы. Поэтому рассмотрим решение, работающее быстрее.
Построим граф из интересных нам этажей. Интересными будем считать следующие этажи:
- Этаж, на котором работает Артем, в той башне, в которой работает Артем;
- Этаж, на котором работает Дмитрий, в той башне, в которой работает Дмитрий;
- Этажи, которые являются концами ближайших переходов к Артему, в сумме получаем 2 вершины от перехода, который выше, и 2 вершины от перехода, который ниже;
- Добавляем 4 вершины аналогично предыдущему пункту, но только для Дмитрия.
Ребра в нашем графе бывают двух типов:
- Рёбра, соединяющие вершины, между которыми есть переход. Веса у таких рёбер равны t;
- Рёбра, соединяющие вершины из одной башни. Веса у таких рёбер равны расстоянию между соответствующими этажами умноженному на скорость лифта в соответствующей башне.
Заметим, что в нашем графе 10 вершин. Искать кратчайшие пути в этом графе можно любым методом, проще всего было реализовать алгоритм Флойда.
Задача B. Депозит.
Идея: Борис Минаев.
Реализация: Виталий Демьянюк.
Разбор: Виталий Демьянюк.
Покажем, что Василию Ивановичу всегда выгодно хранить все деньги в одном банке. Рассмотрим оптимальный ответ. Допустим, что существует год, в котором деньги Василия Ивановича лежали в нескольких банках. Среди всех таких лет выберем год с минимальным номером. Для каждого банка рассмотрим, какой процент через m лет мы получили от денег, лежащих на протяжении рассматриваемого года в этом банке без учета всех комиссий. Выберем банк с максимальным таким процентом. Заметим, что если в него положить все деньги, то ответ не ухудшится, так как в каждом году множество банков, участвовавших в перераспределении средств, будет подмножеством множества банков в оптимальном ответе.
С учетом только что доказанного факта задача решается методом динамического программирования. Пусть dpi,j равно максимальному количеству рублей на счетах Василия Ивановича через i лет, если на протяжении i-года все его деньги лежали в j-том банке. Переберем все банки, где деньги могли храниться в предыдущем году, тогда dpi,j = (max(dpi-1,k -ak) - aj) · pj,i по всем k от 1 до n.
Отдельно рассмотрим случай, когда j = k, тогда dpi,j = max(dpi,j, dpi,j-1 ·pj,i). Ответ равен максимальному dpm,k по всем k. В итоге на первый взгляд решение работает за O(n2m). Однако заметим, что dpi-1,k - ak не зависит от j. Поэтому, если для всех i поддерживать max(dpi-1,k - ak), то получим решение за O(nm).
Задача C. Кеплер
Идея: Виталий Аксёнов.
Реализация: Андрей Комаров.
Разбор: Андрей Комаров.
Изучим, какого размера может получаться панель при всех возможных складываниях. Зафиксируем одну из сторон и посмотрим, как будет со временем изменяться её длина. Рассмотрим на примере стороны, соответствующей первому измерению. Пусть изначально панель имела размер n×m. Интересующая сторона имеет длину n. Далее возможны два случая:
- Складываем вдоль стороны длины n. В этом случае новая длина равна ⌊n/2⌋
- Складываем вдоль стороны длины m. В этом случае длина не изменится и останется равной n
Значит, сторона может иметь только длину n, ⌊n/2⌋, ⌊⌊n/2⌋/2⌋, ..., 1. Их всего N=⌊log2 n⌋+1. Проведя аналогичные рассуждения для второй стороны, получим, что её длина может принимать M=⌊log2 m⌋+1 значений.
Для решения задачи воспользуемся идеей динамического программирования. Обозначим за ai, i∈[1..N] длины, которые может иметь первая сторона, упорядоченные по убыванию. Аналогично для bi.
Посчитаем следующую величину: dp[i, j] = "наименьшее время, за которое можно получить из панели n×m панель ai×bj". Тогда, так как a и b упорядочены по убыванию, aN=1, bM=1, а значит, ответ на задачу равен dp[N, M]. dp[i, j] можно посчитать по правилам:
- dp[i, j]=∞
- dp[i, j]=min(dp[i, j], dp[i-1, j] + ((ai-1 mod 2) · bj), если i>1
- dp[i, j]=min(dp[i, j], dp[i, j-1] + ((bj-1 mod 2) · ai), если j>1
- dp[1, 1]=0
Задача D. Тест.
Идея: Артем Васильев.
Реализация: Артем Васильев.
Разбор: Артем Васильев.
В данной задаче требовалось перечислить все строки из 0 и 1 длиной n так, чтобы выполнялись два свойства: во-первых, количество единиц неубывает, во-вторых, две соседние строки отличались не более, чем в двух позициях.
Напишем процедуру solve(n, k), которая возвращает список всех битовых строк длины n, который удовлетворяет нашему свойству, и в каждой из строк ровно k единиц. Опишем рекурсивный алгоритм для solve(n, k):
- Если k равно 0 или n, то вернем список из единственной строки 0n или 1n.
- Иначе, вызовем рекурсивно solve(n - 1, k) и solve(n - 1, k - 1).
- Ко всем битовым строкам из первого списка припишем в конец 0, ко всем элементам из второго списка припишем в конец 1.
- Вернем конкатенацию первого списка и развернутого второго.
Теперь решение заключается в том, чтобы для всех k от n до 0 вывести все битовые строки из списка solve(n, k). Докажем корректность этого решения. Рассмотрим свойства списка, который возвращает процедура solve.
- Первый элемент списка solve(n, k) равен 1k0n - k.
- Если k > 0, то последний элемент списка solve(n, k) равен 1k - 10n - k1.
- Если k = 0, то последний элемент списка solve(n, k) равен 0n.
Несложно доказать все эти свойства по индукции. В итоге получаем, что процедура solve(n, k) действительно работает корректно. Осталось убедиться, что последний элемент solve(n, k + 1) и первый элемент solve(n, k) различаются не более, чем в двух позициях. Последний элемент solve(n, k + 1): 1k0n - k - 11, первый элемент solve(n, k): 1k0n - k. Легко видеть, что для этих двух строк свойство тоже выполняется. Таким образом мы получили алгоритм, который перечисляет все битовые строки длины n и удовлетворяет необходимым свойствам.
Задача E. Лазеры.
Идея: Анна Малова.
Реализация: Артем Васильев.
Разбор: Артем Васильев.
Основная идея решения — двоичный поиск по ответу. Зафиксируем максимальный угол отклонения α и проверим существование решения с таким ответом. Для этого сведем задачу к поиску пути в графе.
Обозначим точки, заданные в условии, как P1, P2, ..., Pn. Построим граф из n(n - 1) вершин, где каждая вершина это упорядоченная пара различных точек. Проведем ребра из пары (i, j) в пару (j, k) в том случае, если угол между векторами PiPj и PjPk не превосходит α. В построенном графе не более, чем n3 ребер. Теперь, если в таком графе существует путь из вершины вида (1, i) в вершину вида (j, 1), то при таком α эксперимент осуществить можно. Проверять существование пути можно с помощью любого алгоритма поиска пути в графе.
Таким образом, одна итерация двоичного поиска работает за O(n3), а значит, весь алгоритм можно реализовать за O(n3 log(1/ε)) или, если заметить, что пространство поиска на самом деле дискретно (нас интересуют лишь не более, чем n3 углов), то этот алгоритм можно реализовать за O(n3 log(n3)).
Задача F. Колесо
Идея: Борис Минаев.
Реализация: Борис Минаев.
Разбор: Борис Минаев.
Формализуем условие задачи. Без ограничения общности будем считать, что столицу захватила первая оппозиционная партия. Всем остальным городам сопоставим одно число. Это будет 0, если столицу и город захватила одна партия, и 1 в противном случае. Тогда всю информацию о захваченных городах можно представить в виде битовой маски длиной n. Все возможные битовые маски равновероятны. Для конкретной битовой маски ответ находится очевидным образом — максимум из (количества нулей + 1) и (максимального количества единиц, которые идут подряд в маске). При этом следует учитывать, что маска зациклена — следующий бит после последнего первый.
Для того, чтобы получить полиномиальное решение будем использовать динамическое программирование. Его параметрами будут:
- количество цифр в маске
- количество нулей в маске
- максимальное количество единиц подряд
- текущее количество единиц подряд
Однако, чтобы учитывать зацикленность маски необходимо также хранить количество единиц, которые находятся в самом начале маски. Это решение работает за O(n5).
Для того, чтобы избавиться от последнего параметра воспользуемся следующим соображением. Маску, которая состоит из всех единиц обработаем отдельно. Рассмотрим все оставшиеся маски. В каждой из них есть хотя бы один ноль. Сдвинем биты маски по циклу так, чтобы на первом месте оказался первый ноль. Теперь обрежем первый бит. Сейчас некоторые маски повторяются несколько раз. Несложно заметить, что маска повторяется (количество единиц, на которые она заканчивается + 1) раз. Значит, мы можем перебирать все маски длиной n - 1, дописывать слева от нее ноль, а вероятность ее появления умножать на (количество единиц, на которые она заканчивается + 1). Заметим, что число, на которое надо умножить вероятность, мы уже храним в динамическом программировании. Избавившись от одного параметра, получаем решение за O(n4).
Рассмотрим, что происходит с ответом при увеличении количества городов. С очень большой вероятностью, максимальной компонентой связности является компонента, в которую входит столица. А, значит, асимптотически ответ стремится к (n + 2) / 2. Несложно показать, что уже при n = 150 отклонение от асимптотического ответа составляет около 10-12. Также можно доказать, что при увеличении n, отклонение от асимптотического ответа будет только уменьшаться.
Таким образом, получаем следующее решение. С помощью динамического программирования за O(n4) находим ответы для первых 150 значений n. Для больших n используем формулу (n + 2) / 2.