Автор RussianCodeCup, история, 8 лет назад, По-русски

Всем привет!

Завершился Russian Code Cup 2016, задачи финала оказались очень сложными, но участники не спасовали. Победил Геннадий Короткевич — tourist, второе место занял Владислав Епифанов — vepifanov, а замкнул призовую тройку Николай Калинин — KAN. Поздравляем победителей, официальные результаты финала на сайте http://russiancodecup.ru

Перед тем как перейти к разбору задач, хочется поблагодарить Mail.Ru Group за организацию турнира и призы, жюри из Университета ИТМО за подготовку задач. Председатель жюри Андрей Станкевич andrewzta, члены жюри Виталий Аксенов Aksenov239, Николай Будин budalnik, Артем Васильев VArtem, Николай Ведерников Niko, Илья Збань izban, Борис Минаев qwerty787788, Илья Пересадин pva701, Дмитрий Филиппов DimaPhil, Григорий Шовкопляс GShark.

Наконец, разбор задач. Мы приводим краткий разбор, отправляя за деталями к кодам решений жюри, опубликованным вместе с официальными тестами на сайте http://russiancodecup.ru

A. Церемония закрытия

Эта задача решается жадным алгоритмом. Давайте отсортируем людей из первой очереди по тому, сколько они готовы пройти. И будем их поочерёдно расставлять, выбирая каждый раз место следующим образом: среди незанятых мест, до которых он может дойти, выберем то, которое наиболее удалено от второй очереди (точки с координатами (0, m + 1)). После распределения мест для первой очереди, мы сортируем людей из второй очереди и жадно расставляем их по свободным местам, начиная с минимального.

В задаче проходили грамотно написанные решения с асимптотикой не более O((nm)2), которые соответствуют описанию этого жадного алгоритма. Если пользоваться сетом, то асимптотику можно сократить до O(nmlog(nm))

B. Кактусофобия

Разобьем граф на блоки — циклы и мосты. Нужно из каждого цикла удалить одно ребро, и при этом оставить в графе максимально много различных цветов.

Построим двудольный граф, в котором левая доля — блоки, правая — цвета. Из блока проведем ребра в каждый цвет, который есть в этом блоке (если цвет встречается несколько раз, проведем несколько ребер). Из цветов ребра в сток. Из истока ребра в блок, если блок — мост, то с пропускной способностью 1, иначе с пропускной способностью на 1 меньше длины цикла. Несложно видеть, что максимальный поток в этом графе и будет являться ответом.

Так же в этой задаче есть решение, не использующее потоки, и работающее за O(n), предлагаем придумать его в качестве упражнения.

C. Домашнее задание

Задача решается придумыванием конструктивного алгоритма.

  • Для упрощения разбора случаев для всех n, m < 5 запустим полный перебор. Здесь, чтобы избежать неасимптотических оптимизаций и уложиться в TL, стоило сделать предподсчет.
  • Теперь, не умоляя общности, можем считать, что m ≥ 5.
  • Начнем расставлять звездочки подряд по таблице (по строчкам сверху вниз, в каждой строке слева направо). При добавлении каждой звездочки (кроме крайних) добавляется 4 новых уголка. При добавлении звездочки в конец строки добавляется 3 новых уголка, в начало — 1. Прекращаем процесс как только k становится меньше 4 или как только свободных клеток в таблице остается меньше 5.
  • Дальнейшее развитие событий делится на два случая:
    • осталась свободная строка
    • не осталось свободной строки
  • Первый случай. Раз осталась свободная строка, значит мы прервались, потому что k стало меньше 4. Тогда:
    • k = 0: ничего добавлять не надо, все сошлось
    • k = 1: если в текущей строке поставлено уже  ≥ 2 звездочки, поставим звездочку в начало следующей строки, а если нет, то в конец текущей
    • k = 2: если в текущей строке поставлено уже  ≥ 3 звездочки, поставим звездочку во второй столбец следующей строки, а если нет, то в предпоследнюю клетку текущей
    • k = 3: если в текущей строке поставлено уже  ≥ 4 звездочек, поставим звездочки в первый и третий столбец следующей строки. Если поставлено три, поставим во второй столбец следующей и в предпоследний текущей. Если две — в начало следующей строки и в пред-предпоследний столбец текущей. Если одна — ставим звездочки в m - 1 и m - 3 столбцы текущей строки (нумерация с нуля).
  • Второй случай. В таблице осталось 4 свободных клетки, куда можно поставить звездочки. Несложным перебором случаев можно вывести, что, ставя звездочки в эти клетки, можно получить следующие значения k: {0, 1, 2, 3, 4, 5, 6, 8, 9, 12, 15}. Разбор этих случаев остается читателю в качестве упражнения.
  • Также стоило не забыть про случай прямоугольника 3 × m, где m ≥ 5 и k = 2 * (m - 1) - 8 — в этом случае надо оставить первый столбец пустым, а все остальные полностью заполнить.
D. Слалом

Для начала рассмотрим все возможные пути из начальной клетки в конечную, в которых перемещение из клетки идет либо вверх, либо вправо, а также они не пересекают препятствия. Введем понятие класса эквивалентности путей. Пути находятся в одном классе, если они не являются различными по определению из условии задачи. В данной задаче требовалось найти количество таких классов.

Решать задачу будем методом динамического программирования. Для каждого класса будем рассматривать самый нижний путь: сумма высот клеток, по которым он проходит, минимальна. Состояние динамического программирования — это количество таких нижних путей проходящих через данную клетку. Тогда, когда мы встретим препятствие, все пути, которые проходят через клетки ниже его верха, могут разделится на два в случае, если выше них нет другого препятствия, которое помешает это сделать. То есть при обработке препятствия, в клетку над его верхней границей надо добавить сумму значений по клеткам ниже его верха, пути из которых имеют возможность разделиться.

Наивная реализация этого алгоритма требовала O(nm) памяти и O(nm2) времени, поэтому надо использовать дерево отрезков для оптимизации подсчета суммы при переходах динамического программирования, а также хранить не все поле, а только текущий столбец. Таким образом получается O(m) памяти и время работы составляет O(nlogm).

E. Шифр

Для начала рассмотрим решение, которое находит правильный ответ, но работает долго. Что обозначает, что Боря точно может определить, какое число было изначально? Это значит, что он может отличить его от любого другого числа. Поэтому рассмотрим все другие числа и для каждого найдем первый момент времени, когда его можно будет отличить от заданого. Для этого сравним, как кодируются два числа в первый момент времени. Если они кодируются одинаковым образом, то добавим к каждому единицу и сравним еще раз. Будем так продолжать пока два числа не будут кодироваться по-разному. Если вдруг одно из чисел не помещается на табло, то по условию задачи, Боря об этом узнает из-за громкого звука и, значит, в этот момент сможет отличить его.

Основная идея решения заключается в том, что нам не нужно проверять, что число можно отличить от всех 10n - 1 других. Утверждается, что если мы можем отличить число от такого же, в котором изменили ровно одну цифру, то можем отличить и от всех других чисел. Таким образом необходимо узнать первый момент времени, когда можно отличить число от 9n других.

Заметим, что эту подзадачу тоже можно решать быстрее чем за 10n. Для этого переберем разряд, по которому можно будет отличить два числа, а также его значение. После этого найдем первый момент времени, когда одно из чисел примет именно такое значение и проверим, можем ли отличить два числа. Таким образом получаем решение, которое работает за полиномиальное от количества цифр время.

F. Покрытие массива

Для начала заметим, что в ответ обязательно войдут min(k - n, 0) максимальных по сумме отрезков. Для решения задачи найдем сумму min(k - n, 0) максимальных отрезков и элементы, которые они покрывают, а так же следующие за ними min(k, n) отрезков в явном виде. Сделать это можно с помощью бинарного поиска по значению суммы на отрезке и структуры данных вроде дерева Фенвика: для заданного значения суммы дерево Фенвика поможет найти и количество отрезков с хотя бы такой суммой, и сумму отрезков с хотя бы такой суммой, и множество покрытых элементов, и перечислить все отрезки с такой суммой за их количество. Эту часть решения можно сделать за O(nlog2n).

Немного отвлечемся, рассмотрим, как можно решать задачу за n2logn. Пускай мы перебрали x — количество максимальных по сумме отрезков в ответе (O(n) вариантов, поскольку min(k - n, 0) максимальных отрезков войдут в ответ обязательно). Пусть элементы с индексами i1, ..., im остались непокрыты, и нужно их покрыть используя k - x отрезков. Заметим, что каждый из этих k - x отрезков обязан содержать хотя бы один ij-й, при чем два отрезка не могут пересекаться по какому-нибудь ij-му. Можно отсортировать эти k - x отрезков лексикографически, и если l-й отрезок покрывает число ij, а l + 1-й отрезок покрывает ij + 1, то эти два отрезка либо пересекаются по какому-то подотрезку отрезка [ij + 1; ij + 1 - 1], либо не пересекаются по какому-то подотрезку отрезка [ij + 1; ij + 1 - 1]. Таким образом, если для каждого отрезка [ij + 1; ij + 1 - 1] выписать число, равное максимуму из его максимального подотрезка и минус минимального, то наилучшее покрытие — взять весь массив, но не взять минимальный префикс (до i1), минимальный суффикс (от im), и k - x максимальных чисел среди выписанных. Таким образом, решили задачу за n2logn.

Чтобы соптимизировать это решение, можно хранить отрезки [ij + 1; ij + 1 - 1] в упорядоченном множестве. При добавлении нового максимального отрезка нужно удалить несколько ij-х, пересчитать в сете максимальные и минимальные подотрезки для затронутых отрезков (это можно сделать за O(1), зная максимальную сумму на префиксе и суффиксе подотрезка), и взять сумму k - x максимальных чисел из множества. Суммарно удалений ijO(n), поэтому вся эта часть работает за O(nlogn).

Полный текст и комментарии »

  • Проголосовать: нравится
  • +67
  • Проголосовать: не нравится

Автор RussianCodeCup, история, 8 лет назад, По-русски

Всем привет!

Уже завтра, 18 сентября 2016 года, в 12-00 состоится финал RCC-2016. Лучшие 50 участников отборочного раунда сразятся за ценные призы. Продолжительность финала в этом году — 2 часа, финал проходит в режиме онлайн. За ходом финала можно наблюдать на сайте http://russiancodecup.ru

А мы рады объявить, что команда Russian Code Cup совместно с проектом Codeforces приготовили небольшой сюрприз всем тем, кто не прошел на финальный раунд RCC-2016. Сразу после окончания финала, в 14-05 на платформе Codeforces пройдет онлайн-контест по задачам финала.

Контест пройдет по правилам ACM ICPC и будет нерейтинговым. Задачи буду предложены на русском и английском языках. Задачи были подготовлены для финала Russian Code Cup, и поэтому они довольно сложные, контест будет интересен, скорее, участникам из Div1. Разумеется, мы просим всех финалистов не использовать онлайн-контест для дорешивания задач, а, дождавшись окончания, дорешивать в архиве.

Итак, приглашаем всех поболеть за финалистов, а потом ждем всех желающих на онлайн-контесте! Всем удачи!

Полный текст и комментарии »

  • Проголосовать: нравится
  • +90
  • Проголосовать: не нравится

Автор RussianCodeCup, история, 8 лет назад, По-русски

Всем привет!

Напоминаем, что 19 июня 2016 года в 14-00 по московскому времени состоится отборочный раунд Russian Code Cup 2016. В раунде могут принять участие по 200 лучших с каждой из квалификаций, 200 лучших в отборочном раунде получат футболку чемпионата, а топ 50 попадут в финал, который пройдет в сентябре, в финале участники сразятся за денежные призы.

Всем удачи и до встречи на http://russiancodecup.ru !

Полный текст и комментарии »

  • Проголосовать: нравится
  • +62
  • Проголосовать: не нравится

Автор RussianCodeCup, история, 8 лет назад, По-русски
A. Прямоугольник и квадраты

При решении этой задачи нужно заметить, что важна лишь разность площадей исходного прямоугольника и собранного, а форма может быть любая. Тогда очевидно, что в качестве ответа, подойдет прямоугольник со сторонами C и nC, где n — натуральное число.

Число n несложно вычислить. Оно равно частному A·B и C2 округленному либо вверх, либо вниз. Осталось выбрать из этих ответов наиболее выгодный и не забыть учесть, что нужно использовать хотя бы один квадрат C × C.

B. Нули и единицы

Несложно заметить, что операцию инверсии имеет смысл применять только к одной строке: если после применения инверсий первая и вторая строка совпали, то операции со второй строкой можно отменить и применить их к первой строке, после этого строки все еще будут равными. Также заметим, что операции можно применять в любом порядке.

Из этого факта следует простой жадный алгоритм: будем идти по первой строке слева направо и поддерживать ее текущее состояние. Если, находясь в i-м символе, s1[i] ≠ s2[i], тогда следует применить инверсию к символам i, i + 1 первой строки. Если в конце s1[n] ≠ s2[n], то нужной последовательности инверсий не существует и ответ равен  - 1.

C. Новая трасса

Это задача на конструктивное построение примера.

Покажем сначала, как построить пример с максимальным значением k. Начнем с горизонтального отрезка, а затем каждым очередным вертикальным отрезком будем пересекать все предыдущие перпендикулярные ему, кроме того, который идет непосредственно перед ним. Пример для 14 отрезков показан на рисунке.

Чтобы получить ровно k нужно взять 2l отрезков, что l(l - 1) ≥ 2k > (l - 1)(l - 2), и построить максимальный пример, правда, возможно, заранее завершив последний отрезок. Оставшиеся же n - 2l отрезков добавим в начало трассы, разместив вокруг по спирали. Пример приведён на рисунке при n = 17 и k = 9.

Ограничение на k взято не просто так. Это максимальное число пересечений при фиксированном n. Желающие могут доказать это самостоятельно, подсказка: рассмотрите число пересечений пары отрезков n и n - 1 с парами n - 3 и n - 4, n - 5 и n - 6, ..., 2 и 1 (или просто отрезка 1, если n — чётное).

D. Дерево

Сделаем дерево взвешенным и изначально установим у всех ребер вес 1. Теперь заметим, что если вместо удаления вершины изменять на 0 вес ребра, которое из нее идет в ее родителя, то ответ на запрос на поиск расстояния будет равен расстоянию между вершинами из запроса по взвешенным ребрам в исходном дереве.

Научимся решать новую задачу, нужно изменять вес ребра в дереве и находить расстояние между вершинами. Для начала, в исходном дереве найдем расстояния от корня до всех вершин и выпишем эти расстояния в массив в порядке обхода дерева dfs-ом. Будем при каждом изменении веса ребра поддерживать актуальными эти расстояния. Заметим, что все вершины, расстояние до которых изменится при изменении веса какого-то ребра, соответствуют непрерывному отрезку в массиве. Поэтому чтобы обработать запрос, нужно прибавить константу на отрезке в этом массиве. Для такой цели, например, можно построить на этом массиве дерево отрезков.

Тогда расстояние между вершинами можно найти так: dab = da + db - 2·dlca, где lca — это наименьший общий предок a и b, а dv — это расстояние от корня до v.

E. Варвары

Заметим, что недовольство жителей островов, которые находятся в одной компоненте связности, равно их начальному недовольству, умноженному на некоторую величину, которая общая для всех этих островов. Будем поддерживать эти значения для всех компонент.

Научимся обрабатывать удаление ребра за время, пропорциональное размеру меньшей компоненты. Тогда суммарное время работы алгоритма будет . Доказать это можно следующим образом. Для некоторой вершины рассмотрим размер компоненты, в которой она находится. Тогда каждый раз, когда она будет рассмотрена, размер этой компоненты уменьшается как минимум в два раза. Заметим, что каждая вершина будет рассмотрена раз.

Если при удалении ребра мы будем знать, какая из компонент меньшая, то сможем обойти все ее вершины и переместить их в другую компоненту (пересчитав все нужные суммы). Чтобы найти меньшую из компонент, запустим два обхода в ширину в двух компонентах одновременно. А когда один из них посетит все вершины в своей компоненте, не будем продолжать работу второго обхода в ширину. Такой алгоритм работает за время, пропорциональное размеру меньшей из компонент.

Полный текст и комментарии »

  • Проголосовать: нравится
  • +12
  • Проголосовать: не нравится

Автор RussianCodeCup, история, 8 лет назад, По-русски

Всем привет!

Последний в этом году шанс попасть в отборочный раунд Russian Code Cup предоставляется всем в это воскресенье, 5 июня в 16:00. Приглашаем всех, кто еще не прошел в отборочный раунд, принять участие. Удачи и до встречи на Russian Code Cup 2016!

Полный текст и комментарии »

  • Проголосовать: нравится
  • +54
  • Проголосовать: не нравится

Автор RussianCodeCup, история, 8 лет назад, По-русски

Всем привет!

В воскресенье, 29 мая 2016 года, в 12-00 состоится второй квалификационный раунд Russian Code Cup! Мы будем рады видеть на нем всех, кто не прошел в отборочный раунд в первом квале. Ну а те, кто и завтра не сможет пройти в отборочный раунд, смогут принять участие в третьем квалификационном раунде 5 июня. Всем удачи и до встречи на раунде, не забудьте зарегистрироваться на чемпионат на сайте http://russiancodecup.ru

Полный текст и комментарии »

  • Проголосовать: нравится
  • +36
  • Проголосовать: не нравится

Автор RussianCodeCup, 9 лет назад, По-русски

A. Двоичная строка

Для начала заметим, что задача не имеет решений тогда и только тогда, когда выполняется одно из двух условий:

  • Либо |b - c| > 1
  • Либо b = 0, c = 0, и при этом a ≠ 0 и d ≠ 0

Для остальных случаев будем решать в два этапа: Сначала соберем минимальную строку, которая будет удовлетворять условиям на строки 01 и 10, а затем допишем после первого встреченного нуля a - 1 ноль, а после первой встреченной единицы d - 1 единицу. Очевидно, что условия на 01 и 10 от таких действий не испортятся.

B. Поезд и туннель

Разобьем весь поезд на отрезки вагонов без света. Будем идти по каждой группе из таких вагонов, и в том вагоне, на котором суммарная длина вагонов становится не меньше h, будем включать свет. Очевидно, что данная стратегия дает наименьший результат. Также нужно не забыть включить свет в первом и последнем вагонах: при въезде в туннель и выезде из него, соответственно, эти вагоны образуют темный момент времени.

C. Красивое разбиение

Первый элемент массива входит либо в M1, либо в M2. Не умаляя общности, будем считать, что он входит в M1. Тогда a[1] делится на gcd(M1), то есть gcd(M1) — делитель a[1].

Переберем все делители a[1] (их не больше 1344 для чисел до 109). Рассматривая текущий делитель d, возьмем все элементы массива, делящиеся на d, в M1 (так как от этого gcd(M1) не станет меньше d, а чем меньше элементов в M2, тем gcd(M2) больше), а все остальные элементы в M2 и обновим ответ величиной min(gcd(M1), gcd(M2)). Заметим, что мы можем пренебречь тем, что M2 будет пустым, считая в этом случае gcd для него равным бесконечности, поскольку все элементы в таком случае делятся на d, то и gcd(M2) будет не меньше d.

Асимптотика решения — O(sqrt(a[1]) + d(a[1])·n), где d(a[1]) ≤ 1344.

D. Подготовка задач

Для начала решим задачу, если нет запросов на изменение времен. Из массива ti сгенерируем новый массив cntj, в котором будем хранить количество задач, на которые необходимо потратить j минут, а также предподсчитаем массив его префиксных сумм. Тогда количество времени, которое потратит каждый друг, если их всего k, можно посчитать за O(MAX / k), где MAX — максимальное необходимое на задачу время (просто перебрав, на какие задачи потребуется 1, 2, ... MAX / k минут). Как известно, суммарное время работы такого алгоритма для всех k от 1 до MAX равно O(MAXlog(MAX)).

Теперь посмотрим, что происходит, когда время, необходимое на задачу, изменяется с t на t + 1. Ответы поменяются только для k являющихся делителями t. Поскольку максимальное количество делителей у чисел до 5·105 равно 200, то можно просто их все перебрать за время пропорциональное их количеству и обновить ответ только для них.

Аналогично, если время изменяется с t на t - 1, то ответы меняются для чисел, которые являются делителями t - 1.

E. Похожее метро

В задаче требовалось найти наибольшую по размеру пару изоморфных друг другу связных поддеревьев данных деревьев. Будем решать эту задачу методом динамического программирования.

Посчитаем для пары ориентированных ребер (u1, v1) в первом дереве и (u2, v2) во втором дереве значение dp[u1][v1][u2][v2], равное размеру наибольшей пары изоморфных поддеревьев, если вершина u1 перейдет в вершину u2, а вершины v1 и v2 обязательно не войдут в выбранные поддеревья. Кроме того, разрешим vi быть равной какой-нибудь фиктивной вершине -1, это будет значить, что удаленного поддерева vi нет, и можно использовать для общего поддерева все вершины. Если мы посчитаем такую величину, ответом будет максимум по всем парам вершин u1, u2 величины dp[u1][-1][u2][-1].

Чтобы посчитать dp[u1][v1][u2][v2], нужно каким-то образом сопоставить поддеревья u1 и u2 друг другу. Заметим, что если e1 — сын вершины u1, отличный от v1, то поддерево e1 содержит меньше вершин, чем поддерево u1, при подвешивании дерева за v1. Для сыновей e1 вершины u1 и e2 вершины u2 мы по предположению индукции уже посчитали dp[e1][u1][e2][u2], так что мы знаем, сколько вершин можно добавить в искомое поддерево, если вершине e1 в нем будет соответствовать e2. Если у вершины u1 — k сыновей, у u2 — m сыновей, то мы получаем матрицу ai, j размера k·m, в которой нужно выбрать несколько ячеек с максимальной суммой, при условии, что в каждом столбце и в каждой строке выбрано не больше одной ячейки. Это стандартная задача о назначении, которая решается Венгерским алгоритмом.

Итоговая сложность решения — O(n5), n2 способов выбрать пару ребер в двух деревьях и O(n3) — время работы Венгерского алгоритма. Для оптимизации можно не решать задачу о назначении для одинаковых (изоморфных) пар деревьев несколько раз, а запомнить ответ.

Полный текст и комментарии »

  • Проголосовать: нравится
  • +6
  • Проголосовать: не нравится

Автор RussianCodeCup, история, 9 лет назад, По-русски

Всем привет!

Мы рады представить окончательную версию правил Russian Code Cup 2016! Важнейшее нововведение этого года — чемпионат становится международным, теперь задачи предлагаются на русском и английском языках. Все могут принять участие, для этого необходимо зарегистрироваться на сайте http://russiancodecup.ru, участникам прошлых чемпионатов необходимо подтвердить участие в этом году в личном кабинете.

В этом году участники вновь сразятся за звание лучшего программиста и призовой фонд в размере 750 000 рублей. Мы изменили структуру призового фонда, чтобы дать еще больше денежных призов, теперь участники, занявшие места с 11 по 25 также получат призы. Победитель чемпионата получит 150 тыс. рублей, обладатели второго и третьего места — 100 тыс. и 65 тыс. рублей, соответственно. Программистам, занявшим с четвертого по десятое места, достанется по 30 тыс. рублей, с 11 по 25 место – 15 тыс. рублей. 200 лучших участников отборочного раунда получат футболки с символикой чемпионата.

Основная программа чемпионата состоит из трех этапов: квалификационных раундов (8 мая, 29 мая и 5 июня), отборочного тура (19 июня) и финала (18 сентября). На каждом этапе участники олимпиады должны решить от четырех до восьми разноплановых задач. Программисты, которым не повезло в первом квалификационном туре, могут попытать удачу в следующих. В отборочный тур пройдут по 200 лучших участников из каждого квалификационного раунда, а в финале будут сражаться 50 лучших программистов.

Приглашаем всех на первый квалификационный раунд в воскресенье 8 мая, в 19-00 по московскому времени и желаем удачи всем участникам!

Полный текст и комментарии »

  • Проголосовать: нравится
  • +74
  • Проголосовать: не нравится

Автор RussianCodeCup, история, 9 лет назад, По-русски

Всем привет!

Мы рады объявить о том, что в 2016 году Russian Code Cup становится международным соревнованием. Задачи будут предложены на двух языках: русском и английском, приглашаем всех желающих принять участие.

Расписание турнира и информация о призах будут опубликованы на следующей неделе, а пока мы хотим пригласить вас принять участие в разминочном раунде в субботу, 23 апреля, в 18-00 по московскому времени. Регистрируйтесь на сайте http://russiancodecup.ru и участвуйте!

Всем удачи и до встречи на Russian Code Cup 2016!

Полный текст и комментарии »

  • Проголосовать: нравится
  • +260
  • Проголосовать: не нравится