Блог пользователя RussianCodeCup

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

Во-первых, поздравляем jqdai0815 с победой, LHiC и jcvb со вторым и третьим местом. Финал оказался сложным и мы рады, что борьба была жесткой.

Задачи для раунда придумали и готовили Aksenov239, GShark, izban, manoprenko, niyaznigmatul, qwerty787788 и SpyCheese, руководитель жюри andrewzta. Отдельно хочется отметить Илью Збаня izban, который является автором задач D, E и F, а также дал огромное количество полезных комментариев по всем задачам раунда.

Теперь перейдем к разбору.

A. Теория множеств

Для начала докажем, что ответ всегда YES.

Будем последовательно перебирать bj и проверять, что при суммировании его со всеми ai мы не получим сумму, которую уже можно получить другим способом.

Оценим максимальный элемент в множестве B. Посмотрим, почему некоторый элемент bj2, может не подходить. Это означает, что ai1 + bj1 = ai2 + bj2, то есть bj2 = bj1 - (ai2 - ai1). Каждый элемент B таким образом запрещает O(n2) различных значений, из чего следует, что max(B) есть O(n3) в худшем случае, поэтому, в частности, при заданных ограничениях ответ всегда существует.

Теперь ускорим проверку, что очередной элемент подходит. Заведем массив bad, в котором будем отмечать числа, которые в дальнейшем не подойдут для множества B. Теперь при переборе bj, если текущее число не отмечено в bad, то его можно взять в ответ. В дальнейшем в ответ нельзя будет взять числа, которые равны bj + ai1 - ai2, отметим их в массиве bad. Сложность решения O(n3).

B. Похожие слова

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

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

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

Рассмотрим два способа построения этого графа.

Способ 1. Хеши

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

Способ 2. Ахо-Корасик

Построим автомат Ахо-Корасик для данного набора строк. Вершинами нашего графа будут вершины автомата. Заметим, что ребро в нашем графе следует провести между двумя вершинами тогда и только тогда, когда глубины этих вершин отличаются на 1, и из одной в другую ведет суффиксная ссылка.

C. Одиннадцатилетие

Воспользуемся признаком делимости на одиннадцать. А именно нам необходимо, чтобы сумма цифр, стоящих на нечетных позициях, равнялась сумме цифр на четных позициях по модулю одиннадцать. Значит, во-первых, из числа на карточке нас интересует только сумма его цифр по модулю одиннадцать, где цифры на четных позициях взяты со знаком плюс, а на нечетных — со знаком минус. Во-вторых, для каждой карточки нам интересна четность количества цифр в карточках до нее, а вся остальная информация о других карточках не нужна.

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

Если нет ни одной карточки с числом нечетной длины, то вне зависимости от расстановки карточек, остаток от деления на 11 будет один и тот же, так что ответ равен либо 0, либо n!. Иначе, каждое число четной длины можно взять как с плюсом, так и с минусом. Также воспользуемся аналогичным методом динамического программирования, чтобы посчитать количество способов получить некоторый остаток по модулю 11 с помощью карточек с числами четной длины.

В конце объединим результаты для чисел четной и нечетной длины и посчитаем только те варианты, где суммарный остаток от деления на одиннадцать равен нулю. Итого суммарное временем работы O(n2).

D. Кактусомания

Для решения воспользуемся динамическим программированием на корневом дереве. Пусть fv — максимальное число ребер, у которых оба конца лежат в поддереве с корнем в вершине v, и при их добавлении это поддерево становится кактусом.

Для подсчета функции f нужно разобрать два случая: вершина v покрыта каким-нибудь циклом или нет. Если вершина v не покрыта циклом, то fv равно сумме fu, для всех u детей вершины v.

Если же вершина v покрыта циклом, то нужно перебрать, каким именно циклом она покрыта. Для этого переберем все ребра (x, y) такие, что LCA(x, y) = v. Перебрав добавляемое ребро, нужно мысленно удалить этот путь из x в y, посчитать сумму fu по всем u — корням получившихся после удаления этого пути поддеревьев, и прибавить к ней красоту рассматриваемого ребра.

Так мы получили решение за O(nm).

Для ускорения решения придется воспользоваться структурами данных. Во-первых, надо будет вычислить LCA (наименьшего общего предка), чтобы понять, когда следует пытаться добавить это ребро. Любой достаточно быстрый стандартный алгоритм подойдет. Во-вторых, надо быстро вычислять сумму fu всех поддеревьев, получившихся после удаления пути.

Для этого заведем вспомогательные значения: gu = fp - fu, где p — родитель u в дереве, а также sv = sum(fu), где u — дети вершины v. Тогда сумма fu всех поддеревьев после удаления — это сумма значений sx, sy, sv - fx' - fy', суммы на пути [x, x') значения gi и суммы на пути [y, y') значения gi, где x' — это ребенок v, в поддереве которого лежит x и y' — ребенок v, в поддереве которого лежит y. Понадобится структура данных, которая поддерживает изменение в вершине и сумму на пути. Подойдет любая структура для суммы на отрезке, дерево отрезков или дерево Фенвика. Время работы: O((n + m)log(n)).

E. Спутники

Любая точка X задаётся двумя углами α = XAB и β = XBA.

Точка 2, β2) находится в зоне покрытия спутника в точке 1, β1), если α2 ≤ α1 и β2 ≤ β1.

Пусть два спутника, между которыми нужно установить связь, находятся в точках 1, β1) и 2, β2). Ретранслятор должен находиться в такой точке 0, β0), что α0 ≤ min1, α2) и β0 ≤ min1, β2). Поскльку требуется, чтобы он не находился в зоне видимости других спутников, следует выбирать α0 и β0 как можно больше: α0 = min1, α2), β0 = min1, β2).

Перейдём к решению. Рассмотрим запрос типа 3: даны два спутника 1, β1) и 2, β2). Нужно проверить, что точку 0, β0) = (min1, α2), min1, β2)) не видит ни один другой спутник. Это означает, что среди спутников с α ≥ α0 максимальный β меньше β0.

Отсортируем все спутники из всего теста по α. В каждый момент времени для каждого спутника будем хранить его β, если он сейчас существует, и  - ∞, если нет. Построим на этом дерево отрезков на максимум. При добавлении и удалении спутника будем обновлять значения. При запросе типа 3 будем запрашивать максимум на суффиксе. Чтобы не учитывать два спутника из запроса 3, перед запросом максимума присвоим в их ячейки  - ∞, а затем вернём соответствующие β.

В этом решении используются числа с плавающей запятой. От них следует избавиться. Для этого не будем явно считать углы. Сортировать по α будем при помощи векторного произведения. Вместо того, чтобы хранить в дереве отрезков β, отсортируем спутники по β при помощи векорных произведений и будем хранить индексы.

Кроме того, нужно проверять, что точка 0, β0) не лежит внутри планеты. Точка X лежит внутри, если угол AXB тупой, то есть, скалярное произведение XA и XB меньше нуля. Точку X плохо искать в явном виде, так как её координаты не обязательно целые, но векторы, коллинеарные XA и XB, найти можно — это некоторые из векторов, соединяющие точки запроса и точки A и B.

F. Играть или не играть

Перейдем в координаты y(x), где x = exp1 - exp2, а y = exp1 (exp1, exp2 — опыт первого и второго игроков в некоторый момент времени соответственно). Будем идти по отрезкам времени и поддерживать множество достижимых состояний на этой плоскости.

Лемма 1: есть оптимальное решение, в котором на всех отрезках, где могут играть оба, оба играют (доказательство в конце).

Есть три перехода:

  • Может играть лишь первый игрок t времени. Это значит, что новый многоугольник — сумма Минковского старого многоугольника и вырожденного многоугольника на двух вершинах (0, 0) и (t, t).
  • Может играть лишь второй игрок t времени. Это значит, что новый многоугольник — сумма Минковского старого многоугольника и вырожденного многоугольника на двух вершинах (0, 0) и ( - t, 0).
  • Могут играть оба игрока t времени. Поскольку не выгодно, чтобы играл лишь один из них (лемма 1), нужно всем точкам с координатами в [ - C;C] прибавить 2t в y-координату, а остальным точкам прибавить t в y-координату.

Посмотрим на структуру множества достижимых точек. Это многоугольник, монотонный относительно оси OX (любая прямая параллельная OY пересекает его в  ≤ 2 точках), при чем нижняя ломаная этого многоугольника — y = 0 при x ≤ 0 и y = x при x > 0. Эти два факта непосредственно следуют из того, что все три операции сохраняют эти инварианты. Таким образом, нам интересно следить лишь за верхней огибающей многоугольника.

Покажем, что y-координата верхней части многоугольника у нас будет не убывать с ростом x-координаты, и состоит верхняя часть ломаной из векторов (+1,0) и (+1,+1). Пусть по индукции это верно.

После переходов первых двух типов монотонность сохраняется на всей ломаной: первый переход лишь сдвигает старую верхнюю часть ломаной на (+1, +1), а второй переход сдвигает верхнюю часть ломаной на (-1,0). Но вот операция третьего типа может нарушить монотонность в точке x = C, так там происходит сдвиг по OY на разные величины. Исправим это, несколько изменив смысл многоугольника, который мы поддерживаем: если раньше это было множество всех достижимых состояний, то теперь будем поддерживать монотонное множество точек, возможно не все из которых можно получить, но имеющее такой же максимальный достижимый опыт из любой точки множества.

Для этого докажем лемму: если в момент времени t взять две точки (не важно, достижимы ли они к моменту времени t) P1 = (x1, y1) и P2 = (x2, y2) такие, что C ≤ x1 ≤ x2, и y1 ≥ y2, то ответ для точки P2 не больше, чем ответ для точки P1. Для этого возьмем сертификат, ведущий к оптимальному ответу из точки P2 — это некоторая последовательность переходов первого, второго и третьего типа, и выкинем из нее первых переходов второго типа ровно на x2 - x1 (единственных, уменьшающих x). Можно заметить, что полученный сертификат — корректный набор действий из точки P1, и имеет такой же сдвиг по y, так как области двойного опыта будут ровно такими же (до момента совпадения x-координат его быть не может, т.к. x2 - C ≥ x2 - x1, а с этого момента времени графики движения будут совпадать).

Таким образом, после операции третьего типа можно взять на суффиксе x для [C; + inf) максимум между сдвинутой ломаной и прямой y = y(C). Абсолютно аналогично доказывается, что ломаную можно промажорировать прямой y = y(C) - (C - x) на отрезке ( - inf;C].

Таким образом, после перехода ломаная все еще осталась монотонно неубывающей, состоящей из ребер (+1,0) и (+1,+1).

Для решения задачи нужно научиться быстро совершать необходимые переходы с этой ломаной.

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

Можно хранить ломаную в декартовом дереве — тогда легко разрезать ломаную в точках  - C и  + C, вставить в нужное место новые ребра и удалить несколько старых. Таким образом, получили решение, работающее за O(nlogn).

Лемма 1, доказательство: рассмотрим оптимальное решение, и посмотрим на первый отрезок, где могли играть оба, но играет лишь один. Если |x| = |exp1 - exp2| ≤ C, это делать точно не выгодно. Иначе, для определенности, x <  - C. Понятно, что в оптимальном решении точка еще вернется в полосу |x| ≤ C (иначе оба могут спокойно играть). Если сейчас играет лишь первый игрок (прибавляется вектор (+1, +1)), то раньше, естественно, был момент после последнего посещения полосы двойного опыта, когда играл лишь второй игрок (вектор (-1, 0)). Пусть в тот момент второй игрок не играл, а сейчас играют оба, итоговая позиция сохраняется, а первый момент, когда играет один, но не оба, отдалился. Случаи, когда играет лишь первый игрок, и x > C, разбираются абсолютно аналогично, убирая момент времени, когда играет лишь один из игроков, но добавляя момент, когда играют оба.

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

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

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

Всем привет!

10 сентября 2017 года, в 13:00 по московскому времени состоится финал Russian Code Cup 2017! Раунд продлится 3 часа. 55 участников финального раунда сразятся за призы, и мы все с нетерпением ждем возможности поболеть за них на сайте http://russiancodecup.ru.

Ну а для всех остальных участников мы подготовили сюрприз: если вы хотите попробовать свои силы в решении задач, приходите на codeforces.com после финала, в 16:35 по московскому времени состоится раунд по задачам RCC 2017, открытый для всех желающих.

Несколько уточнений:

  • Раунд пройдет по правилам ACM;
  • Раунд будет нерейтинговым;
  • Сложность задач будет близка к Div 1 раунду;
  • Мы просим участников финала после окончания официального соревнования воздержаться от публикации и обсуждения задач до окончания раунда-трансляции, и, разумеется, в нем не участвовать;
  • Производительность инвокеров сайта RCC и CF различная, "у меня прошло/не прошло решение на CF, а на RCC было по-другому" не может быть поводом для апелляции.

UPD Официальное соревнование завершено, поздравляем победителей! Всем остальным удачи на раунде.

UPD2 Разбор задач

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

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

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

A. Маленькие числа

Для начала разложим числа a и b на простые.

Теперь заметим, что если какое-то простое число p входит в произведение ab в степени выше первой, то мы можем либо сразу поделить оба числа на число p, либо если же одно из чисел не делится на p, то перенесём в него множитель p из другого числа, после чего поделим на p.

Очевидно, что чётность вхождения простых чисел в произведение ab не меняется во всех операциях. Тогда давайте оставим все простые в первой степени. Остались числа p1, p2, ..., pn. Давайте назовём произведение всех этих простых чисел d, d ≤ ab. Утверждается, что этих простых не может быть больше 14. Если 1 ≤ a, b ≤ 109, то произведение ab ≤ 1018, а произведение первых 15 простых чисел превышает 1018) Теперь заметим, что конечный ответ — пара чисел (x, y) таких, что xy = d. Из этого следует, что второй элемент пары определяется однозначно по первому. Переберём всевозможные делители d за число x за O(2n), и выберем наилучшую пару.

B. Новая клавиатура

Используем для решения метод динамического программирования. Состояние — d[i][j][k], где i — флаг, обозначающий тип предыдущего действия (0, если это было переключение раскладки, и 1, если это был набор символа), j — номер текущей раскладки, и k — количество набранных символов. Значение — минимальное время, необходимое, чтобы достичь этого состояния.

Переберем k. Для фиксированного k переберем j от 1 до n два раза. Обновим d[0][j % n + 1][k] = min(d[0][j % n + 1][k], min(d[0][j][k] + b, d[1][j][k] + a)). Перебрать j от 1 до n два раза нужно потому, что после набора k-го символа может быть включена раскладка с номером большим, чем раскладка, в которой будет набран следующий символ, и будет необходимо произвести переключение раскладок по циклу до нужной. После этого переберем j еще раз и обновим значения для k + 1. Если в j-й раскладке есть k-й символ сообщения, d[1][j][k + 1] = min(d[0][j][k], d[1][j][k]) + c.

В конце ответ равняется min(d[1][j][m]), где m = length(s), по всем j от 1 до n.

C. Складывание фигуры

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

Возьмем любую из клеток сложенной фигуры с минимальной координатой по оси OX — клетку (xi, yi). За линию сгиба возьмем вертикальную прямую x = xi, касающуюся этой клетки. Теперь слева нужно восстановить k - n клеток первоначальной фигуры, чтобы после складывания левой части вдоль линии сгиба на правую получилась данная во входных данных сложенная фигура. Так как k - n ≤ n (иначе после сгиба получилось бы больше n клеток), достаточно выделить из сложенной фигуры k - n клеток, образующих связную фигуру, содержащую клетку (xi, yi). Это можно сделать простым обходом в глубину.

D. Остроугольные треугольники

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

Общее количество треугольников, которые можно построить с вершинами в заданных точках равно Cn3.

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

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

Время работы решения: O(n2log(n)).

E. Объединение массивов

Приведем два решения этой задачи — за O(k2·log(k)) и O(k2).

Решение за O(k2·log(k)):

Разобьем решение задачи на три пункта:

  • 1) Для каждого массива X (A или B) и каждой длины 1 ≤ length ≤ |X| найдем minSubsequenceX[length] — лексикографически минимальную подпоследовательность X длины length;
  • 2) Переберем длину подпоследовательности в первом массиве — 1 ≤ t ≤ min(k - 1, |A|). Если 1 ≤ k - t ≤ |B|, возьмем minSubsequenceA[t] и minSubsequenceB[k - t], их надо объединить;
  • 3) Объединим две подпоследовательности в одну, получив тем самым лексикографически минимальную подпоследовательность длины k, обновим ответ.

1) Чтобы найти minSubsequenceX[length] для каждого length, выполним следующие пункты:

  • Посчитаем next[i][c], в котором будет храниться следующее после i вхождение символа c в X;
  • Посчитаем firstSymbol[length][i] — первый символ лексикографически минимальной подпоследовательности массива X[i..|X| - 1] длиной length. Для этого заметим следующее:
    • Если j1 = next[i][1] существует, то firstSymbol[1][i], firstSymbol[2][i], ... firstSymbol[|X| - j1][i] начинаются с 1;
    • Если j2 = next[i][2] существует, то firstSymbol[|X| - j1 + 1][i], ..., firstSymbol[|X| - j2][i] начинаются с 2;
    • ...
    • Если j|alphabet| = next[i][|alphabet| существует, то firstSymbol[max(|X| - j1, |X| - j2, ..., |X| - j|alphabet| - 1) + 1][i], ..., firstSymbol[|X| - j|alphabet|][i] начинаются с |alphabet|.
    где alphabet — максимальное возможное число в массиве X.
  • Посчитав firstSymbol[length][i], можно восстановить лексикографически минимальную подпоследовательность X для каждой длины итеративно по одной букве.

Этот пункт работает за O(|X|2).

3) Найдя две лексикографически минимальные подпоследовательности SA и SB, их надо объединить в одну лексикографически минимальную длиной k. Будем двигаться по подпоследовательностям двумя указателями p1 и p2. Если SAp1 ≠ SBp2, то двигаем указатель, стоящий на меньшем числе. Если SAp1 = SBp2, двоичным поиском найдем наибольший общий префикс SA[p1..|SA|] и SB[p2..|SB|] и сравним следующие числа. Для сравнения подотрезков SA и SB можно использовать хеши.

Этот пункт работает за O((|SA| + |SB|)·log(max(|SA|, |SB|))) = O(k·log(k)).

Итого, суммируя все три пунта, получаем асимптотику O(|A|2 + |B|2 + k2·log(k)) = O(k2·log(k)).

Решение за O(k2):

Будем называть массив A нулевым, а массив B — первым. Будем строить ответ по одному элементу. Также будем поддерживать вспомогательное значение dp[i][j], где i — номер массива (0 или 1), а j — индекс в этом массиве. dp[i][j] равно минимальному индексу в массиве 1 - i, с которого можно продолжать строить ответ, если в массиве i мы остановимся на индексе j.

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

После добавления найденного элемента v в ответ, за O(|A| + |B|) обновим значения dp. Для обновления будем пользоваться посчитанным в предыдущем решении массивом next.

F. Два поддерева

По условию, в k-поддереве обязательно есть вершины на глубине k. Временно отменим это требование.

Рассмотрим все k-поддеревья для некоторого k. Их можно разбить на классы эквивалентности. Каждой вершине поставим в соответствие ck[v] — метку класса эквивалентности, которому принадлежит её k-поддерево.

При k = 0 все c0[v] равны, так как 0-поддерево любой вершины — это она сама.

При k = 1 c1[v] равно количеству детей вершины.

Научимся по массивам ck[v] и cm[v] строить массив ck + m[v]. Для начала, поставим в соответствие каждой вершине v массив arrk + m[v], который будет однозначно задавать класс эквивалентности её k + m-поддерева. Пусть u1, ..., us — потомки вершины v на расстоянии k в порядке обхода dfs. Тогда поставим в соответствие вершине v массив arrk + m[v] = ck[v], cm[u1], ..., cm[us]. То есть, k + m-поддерево вершины задаётся k-поддеревом вершины и m-поддеревьями нижней части k-поддерева. Ниже приведена иллюстрация для k = 3 и m = 1.

Чтобы получить для каждой вершины список её потомков на расстоянии k, запустим поиск в глубину от корня. Будем поддерживать в стеке путь до корня и каждую вершину класть в массив для её предка на расстоянии k.

Чтобы преобразовать массивы arrk + m[v] в числа ck + m[v], можно захешировать их, использовать бор или unordered_map из массива в номер. Время работы будет O(n), поскольку каждая веришна встречается в списках arr только один раз.

Имея массив ck[v], можно легко проверить, что существует два одинаковых k-поддерева. Для этого нужно найти две вершины с одинаковым ck, при этом нужно рассматривать только вершины, у которых есть потомки на расстоянии k от неё (это то требование, которое мы отменили в начале).

Чтобы найти максимальное k, посчитаем c1[v], c2[v], ..., c2t[v] (2t — максимальная степень двойки, не превосходящая n). После этого используем аналог двоичных подъемов по k: начнём с k = 0 и по очереди попытаемся прибавить к нему 2t, 2t - 1, ..., 20.

Время работы решения: O(nlog(n)).

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

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

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

На старт, внимание, марш!

Настало время определить 50 участников, которые сразятся в финале Russian Code Cup в сентябре. Приглашаем тех, кто прошел квалификацию, принять участие в отборочном раунде в воскресенье, 14 мая 2017 года, в 13-00 по московскому времени. Раунд продлится 2 часа. Ждем всех на russiancodecup.ru, всем удачи!

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

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

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

A. Электронные таблицы

Вычтем из k единицу, перейдя к нумерации столбцов с 0.

Выясним сначала, сколько символов входит в название столбца. Для этого будем последовательно отнимать от k степени числа 26, пока очередная степень не окажется больше оставшегося числа k'.

Теперь для получения имени столбца достаточно перевести k' в 26-ричную систему счисления с цифрами A–Z и дополнить ведущими нулями (точнее символами A) до необходимого числа символов. Это можно сделать стандартными методами языка программирования (правда при этом получатся цифры 0–9, A–P, их надо будет заменить), либо самостоятельно реализовав алгоритм перевода в систему счисления с заданным основанием.

Небольшое замечание: за день до тура один из тестеров указал, что задача B с раунда CF Beta 1 очень похожа на эту задачу. После некоторого обсуждения, учитывая, что раунд 1 на CF был очень давно, наша задача существенно проще, а хорошо подготовленной, достаточно простой замены у нас не было, мы решили оставить эту задачу в раунде.

B. Смертный бой

Посчитаем количество единиц здоровья, которое i-й персонаж до своей гибели может снять боссу в одиночку: pi = ai·⌈ hi / A⌉. После этого отсортируем персонажей по невозрастанию pi и будем посылать их к боссу по одному в этом порядке, пока он не будет убит.

C. Слегка палиндромные числа

Научимся считать количество слегка палиндромных чисел среди первых n натуральных чисел. Чтобы получить количество таких чисел на отрезке с l по r, нужно посчитать их количество среди первых r и l - 1 натуральных чисел и вычесть одно из другого.

Все числа от 1 до 9 слегка палиндромные, поэтому если n ≤ 9, то ответ равен n. В противном случае прибавим к ответу 9 и посчитаем количество слегка палиндромных чисел на отрезке с 10 по n. Рассмотрим десятку чисел от 10k до 10k + 9, где k ≠ 0. Среди них ровно одно слегка палиндромное, так как последняя цифра должна равняться первой цифре k. Таким образом, есть по одному слегка палиндромному числу на отрезках от 10 до 19, от 20 до 29, и так далее.

Пусть n = 10k + d, где 0 ≤ d ≤ 9. Тогда на отрезке от 10 до n находятся k слегка палиндромных чисел, если d не меньше первой цифры k (а она равна первой цифре n), и k - 1, если меньше.

D. Дерево и многочлены

Сформулируем ключевые идеи решения.

Во-первых, заметим, что задачу можно решать независимо для двух типов запросов, поскольку операции не зависят от значений в вершинах.

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

Однако мы не можем выполнять запросы в явном виде, рассматривая все вершины, фигурирующие в запросе, и прибавляя к хранящимся в них многочленах многочлен из запроса — такое решение будет работать за O(n2·k) и не укладывается в ограничения по времени. Поэтому воспользуемся идеей отложенных операций.

В запросах первого типа нужно добавить многочлен q(x) в каждую вершину поддерева v. Для этого просто оставим пометки, что это надо сделать: будем хранить в вершине v многочлен push1[v], при выполнении запроса первого типа прибавим многочлен q к многочлену push1[v].

Аналогично для запросов второго типа будем использовать многочлен push2[v], который для каждой вершины будет хранить сумму многочленов из запросов второго типа к этой вершине.

Пускай теперь sum1[u]  — сумма всех многочленов, которые нужно было учесть в вершине u в запросах первого типа.

Для вычисления sum1[u] выполним поиск в глубину от корня дерева, который будет поддерживать многочлен cur и прибавлять к многочлену cur значение push1[v] при посещении новой вершины v и вычитать его обратно при выходе из неё. Соответственно, находясь в вершине u присваиваем sum1[u] = cur.

Аналогично, обозначим как sum2[u]  — сумма всех многочленов, которые нужно было учесть в вершине u в запросах второго типа.

Для вычисления sum2[u] также поспользуемся обходом в глубину. Значение sum2[u] равно сумме значений sum2[v] для детей u и значения push2[u].

Получив многочлены sum1[u] и sum2[u] в каждой вершине, вычислим их значение от величины d[u] и сложим, результат и будет ответом.

Время работы решения — O(nk).

E. Красивый отчёт

Перед решением основной задачи решим следующую, пока слабо связанную с исходной, задачу: на вход даётся последовательность элементов (a1, a2, ..., an) и нужно посчитать число различных элементов в этой последовательности. Особенностью этой задачи будет то, что:

  1. элементы можно считывать только последовательно и только один раз: после считывания элемента ai последует считывание ai + 1, вернуться к ai будет нельзя;
  2. вы не можете использовать больше O(1) дополнительной памяти.

Чтобы подойти к решению этой подзадачи, вспомним, что математическое ожидание минимума из m равномерно распределённых на отрезке [0;1] случайных величин равно 1 / (m + 1). Например, если вы возьмёте четыре случайных числа из отрезка [0;1], то минимальное из этих чисел в среднем будет равно 0, 2.

Возьмём хеш-функцию h: A → [0;1], отображающую элементы ai в числа на отрезке [0;1]. Посчитаем значение M = min {h(a1), h(a2), ..., h(an)}. В качестве ответа на задачу вернём 1 / M - 1. Проблемой этого решения является то, что нам может «не повезти» и, например, h(ai) окажется очень маленьким, что окажет существенное влияние на ответ. Чтобы сгладить эту проблему, повторим описанный процесс несколько раз для различных хеш-функций h1, h2, ..., hk для некоторого фиксированного k и усредним полученные результаты.

Вернёмся к нашей задаче поиска числа достижимых вершин. Рассмотрим сначала случай ацикличного графа. Ациклический граф отличается от графа с циклами, в том числе, тем, что у него существует топологическая сортировка: такой порядок следования вершин, при котором рёбра графа идут только от вершин, стоящих в порядке позже к вершинам, стоящим в порядке раньше. Существование топологической сортировки позволит для графа, в котором в каждой вершине записано случайное число из отрезка [0;1] посчитать, какое минимальное число достижимо из каждой вершины. Это можно сделать с помощью динамического программирования: рассматривание вершин в порядке топологической сортировки позволит посчитать значение для текущей вершины на основе уже посчитанных достижимых из неё. Посчитав минимальное достижимое число Mi из каждой вершины, можно указать в качестве ответа для данной вершины 1 / Mi - 1. Описанное требуется повторить несколько раз и усреднить результаты.

Однако, в данной задаче в графе могут быть циклы. Чтобы решить эту проблему, построим конденсацию графа. В полученном ацикличном графе в каждой вершине находится, на самом деле, несколько вершин начального графа. Для всех этих вершин ответ будет один и тот же. Отличие в подсчёте ответа будет заключаться в том, что в вершину пишется не случайное число из [0;1], а минимум из wi случайных чисел из [0;1], где wi — число вершин начального графа, содержащихся в i-й вершине сконденсированного.

Более подробно с методом решения этой и некоторых других подобных задач любопытный читатель может ознакомиться в статье "Size-Estimation Framework with Applications to Transitive Closure and Reachability", доступной по адресу http://cohenwang.com/edith/Papers/tcest.pdf.

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

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

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

Всем привет!

Пока более 400 участников уже готовятся сразиться в отборочном раунде Russian Code Cup 2017, тех, кто пока не квалифицировался, мы приглашаем принять участие в третьем квалификационном раунде, который состоится в субботу, 29 апреля, в 14-00. Лучшие 200 участников смогут также принять участие в отборочном раунде и побороться за выход в финал Russian Code Cup 2017.

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

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

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

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

A. Очень важные гости

Есть два способа решить эту задачу.

Первый заключается в том, чтобы рассаживать гостей по диагоналям, начиная от позиции (1, 1). Требуется некоторая аккуратность в реализации, чтобы верно учесть случаи n < m и m < n.

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

B. Наименьшее общее кратное

Пусть p / q нацело делится на a / b и c / d, при этом все дроби несократимые. Тогда целыми числами являются (p / q): (a / b) = (p·b) / (q·a) и (p / q): (c / d) = (p·d) / (q·c).

p·b делится на q·a. Поскольку b и a взаимно просты, p делится на a. p и q тоже взаимно просты, поэтому b делится на q. Аналогично, p делится на с и d делится на q.

Таким образом, p делится lcm(a, c), q делит gcd(b, d). Дробь lcm(a, c) / gcd(b, d) является наименьшей подходящей дробью, и она делится на a / b и c / d. Таким образом, ответ равен lcm(a, c) / gcd(b, d).

C. Портим порядок

В задаче требуется дополнить исходный массив до перестановки чисел от 1 до n таким образом, чтобы сортировка выбором сделала наибольшее число обменов.

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

Таким образом нам нужно дополнить массив до перестановки таким образом, чтобы количество циклов получилось минимальным. Для этого рассмотрим перестановку как ориентированный граф, где если a[i] = j, то в графе есть ребро из вершины i в вершину j. Граф разбился на циклы и цепочки. Все циклы, которые есть в этом графе, сохранятся и после добавления недостающих элементов, а все имеющиеся цепочки (вершину, не имеющую ни входящих, ни исходящих ребер, будем считать цепочкой длины 0) можно объединить в один большой цикл. Пройдемся по всем имеющимся цепочкам и будем добавлять ребро из конца одной цепочки в начало другой. Когда все цепочки соединены в одну, добавим ребро из её конца в начало, и она станет циклом.

D. Красно-черное дерево

Заметив тонкий намек в первом предложении условия, легко доказать (или проверить в любой книге по структурам данных), что в любом красно-черном дереве с n вершинами, на пути от корня до листьев O(logn) черных вершин. Воспользуемся этим фактом в решении.

Назовем почти красно-черным дерево, в котором выполняются все свойства красно-черного дерева, но корень дерева покрашен в красный цвет.

С помощью динамического программирования посчитаем для каждого поддерева количество способов покрасить вершины в нем так, чтобы поддерево было красно-черным, и количество черных вершин на пути от корня до листьев равнялось h, или чтобы дерево было почти красно-черным, и количество черных вершин на пути от корня до листьев равнялось h.

Сохраним это количество способов в массиве d[v][h][t], где v — номер вершины, являющейся корнем поддерева, h — количество черных вершин на пути от корня до листьев, а t обозначает тип покраски, при t равном 0, там хранится количество покрасок, чтобы поддерево стало красно-черным, а при t равном 1, чтобы поддерево стало почти красно-черным.

Тогда d[v][h][0] равняется произведению по всем u, являющимся детьми v, значений (d[u][h - 1][0] + d[u][h - 1][1]). А d[v][h][1] равняется произведению по всем u, являющимся детьми v, значений d[u][h][0].

Ответ на задачу будет равен сумме по всем d[1][h][0].

Теперь заметим, что h для всех допустимых раскрасок поддеревьев ограничено некоторой величиной C = O(log(n)), и при всех h > C, количество покрасок гарантированно будет равняться нулю.

Значит, необходимо всего O(nlog(n)) состояний, из каждого из которых идет O(1) переходов. Следовательно решение будет работать за O(nlog(n)).

E. Изучение массива

Поскольку ограничения на n и q совпадают, будем использовать в n вместо max(n, q). Сделаем несколько наблюдений.

Первое наблюдение: ответ на запрос — максимальная длина отрезка [L, R], такого, что prefL - 1 = prefR, где prefi = a1 + a2 + ... + ai — префиксная сумма массива a (pref0 = 0). Далее будем считать, что мы работаем с массивом pref0, pref1, ..., prefn, и по запросу [l, r] требуется найти максимальный по длине подотрезок [L, R] отрезка [l - 1, r], такой, что prefL = prefR.

Второе наблюдение: prefi — довольно маленькие ( - n ≤ prefi ≤ n).

Будем решать задачу оффлайн. Воспользуемся методом, во многом похожим на алгоритм Мо. Разобьем все запросы на группы, где в i-й группе содержатся запросы [li, ri], такие, что i·K ≤ li < (i + 1)·K (K — константа, приблизительно равная sqrt(n)). В каждой группе отсортируем запросы по правой границе. Теперь решим задачу отдельно для каждой группы запросов.

Рассмотрим i-ю группу [Li, Ri]. Будем идти подряд по запросам слева направо (то есть по неубыванию правой границы). Также будем поддерживать два массива mostLeft[p] и mostRight[p] — текущее самое левое вхождение префиксной суммы p, которое также левее Ri и текущее самое правое вхождение префиксной суммы p. Поддерживая эти два массива, также несложно поддерживать ответ на отрезке [Ri + 1, r], где r — правая граница текущего запроса. Для ответа на запрос [l, r] посчитаем за O(K) ответ на [l, min(r, R)], используя информацию из массива mostRight и сравним это значение с текущим ответом на отрезке [R + 1, r].

Итого, на запросы каждый группы в сумме мы тратим O(K·ci + n) времени, где ci — количество запросов в группе. Так как групп всего O(n / K), получаем суммарное время работы O(sumi = 1... n / K(K·ci + n)) = O(K·sum(ci) + n2 / K). При K = sqrt(n) получаем время работы O(n·sqrt(n))

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

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

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

Всем привет!

Лучшие 200 участников первой квалификации уже в отборочном раунде, а остальные могут попытать свои силы во втором квалификацоинном раунде. Он состоится в это воскресенье, 16 апреля в 12-00 по Московскому времени . Лучшие 200 участников попадут в отборочный раунд, а остальные смогут попытать свои силы еще один раз, в третьем квалификационном раунде.

Желаем всем удачи на раунде и ждем на http://russiancodecup.ru!

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

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

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

A. Марсианский волейбол

Первое наблюдение, которое необходимо сделать — для минимизации длительности игры только команда с наибольшим счетом должна получать очки.

Рассмотрим условие победы одной из команд. Необходимо набрать минимум k очков и обогнать проигравшую команду как минимум на 2 очка.

Итоговый ответ max(k, min(x, y) + 2) - max(x, y).

B. Раскраска стены

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

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

1 2 3 4 1 2 3 4 1 2

2 3 4 1 2 3 4 1 2 3

3 4 1 2 3 4 1 2 3 4

...

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

Если у Маши цветов меньше чем L, то ответа не существует.

C. Магический артефакт

Обозначим как ci выигрыш во времени, который Максим получает от артефакта на i-м уровне: ci = ai - bi.

Пусть уровни проходятся в порядке 1, 2, ..., n. Если артефакт был найден на i-м уровне, то время прохождения равно сумме значений bj плюс c1 + ... + ci. Тогда математическое ожидание времени прохождения равно:

b1 + ... + bn + p1·c1 + p2·(c1 + c2) + ... + pn·(c1 + ... + cn).

b1 + ... + bn не зависит от порядка уровней, поэтому будем стремиться уменьшить оставшуюся часть суммы. Если раскрыть скобки, можно заметить, что она равна сумме ci·pk по всем парам i, k, таким что i ≤ k.

Посмотрим, как изменится сумма, если поменять местами уровни i и i + 1. Среди слагаемых ci·pk изменится только ci·pi + 1 — оно заменится на ci + 1·pi. Если порядок уровней является оптимальным, то ci·pi + 1 ≤ ci + 1·pi (иначе их можно было бы поменять местами и улучшить ответ). Преобразуем это неравенство:

ci / pi ≤ ci + 1 / pi + 1.

В оптимальном ответе для всех i от 1 до n - 1 верно это неравенство. Значит, в оптимальном ответе уровни отсортированы по ci / pi, и чтобы получить его, нужно их отсортировать. Заметим, что если pi = 0, можно считать ci / pi = ∞.

Сортировать по ci / pi нужно осторожно. Если производить деление в числах с плавающей точкой, то в случае, когда ci = pi = 0, получится NaN, что приведёт к ошибке. Если сравнивать дроби ci / pi и ck / pk при помощи компаратора ci·pk < ck·pi, то в случае, когда ci = pi = 0, результат всегда будет false, что тоже приведёт к неправильной сортировке. Поэтому уровни с ci = pi = 0 нужно обработать отдельно. Эти уровни могут быть пройдены в любой момент: нетрудно заметить, что они не вносят никакого вклада в ответ, кроме фиксированных слагаемых bi. Их можно поместить, например, в самый конец.

После сортировки нужно посчитать искомое математическое ожидание. Оно вычисляется по вышеприведённой формуле при помощи префиксных сумм ci.

D. Менеджер памяти

Для начала для каждого запроса qi найдем goi — минимальное j, такое, что среди qj, qj + 1, ..., qi не более k различных чисел — это будет означать, что перед j-м запросом можно так поставить указатели, что j, j + 1, ..., i запросы выполнятся мгновенно. Это делается за O(sum(|qi|)) например двумя указателями, идя с конца массива запросов.

Теперь будем использовать динамическое программирование, вычислим значения dpi — минимальная время, за которое можно выполнить первые i запросов. Если goi = 0, то dpi = 0. Если нет, то несложно видеть, что dpi = minj = goi..i(dpj - 1 + sj) — мы выбираем, запрос j, перед которым будем менять указатели, и платим соответствующую стоимость. Так как goi не убывают, посчитать эту величину можно либо поддерживая set значений dpj - 1 + sj, либо используя очередь с операцией «минимум».

E. ЛИСА

Рассмотрим сначала задачу для двух конкретных строк: посчитаем число вхождений каждой буквы в первую строку, и количество каждой вхождений буквы во вторую строку, не будем при этом учитывать первую букву в первой строке и последнюю букву во второй строке, которые надо взять в любом случае. Чтобы получить ответ, надо вычислить произведение длин строк и отнять от это го значения сумму cnt1[letter] * cnt2[letter] по всем буквам. Доказательство этого утверждения оставим как упражнение, такая задача предлагалась на северном четвертьфинале 2015 http://neerc.ifmo.ru/archive/2015/northern/north-2015-statements.pdf, задача C. Широкий круг участников мог решать ее на кубке трех четвертьфиналов.

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

Осталось научиться отвечать на запросы на отрезке. Будем использовать sqrt-декомпозицию, разобьем строки в группы по сумме длин. Будем жадно набирать в группу строки, пока суммарная длина в группе не станет больше корня из суммы длин, последняя строка может быть достаточно большой.

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

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

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

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

Всем привет!

Уже совсем скоро, в это воскресенье, 2 апреля в 19-00 по московскому времени состоится первый квалификационный раунд Russian Code Cup 2017. Лучшие 200 участников попадут в отборочный раунд, а остальные смогут попытать свои силы еще 2 раза, во втором и третьем квалификационных раундах.

Мы приготовили для вас небольшие новинки: в список языков на раунде будут добавлены Kotlin, Haskell и Free Pascal, а в список интерпретаторов языка Python — интерпретатор PyPy. Точные версии компиляторов и строки компиляции опубликованы в правилах олимпиады на сайте. Мы также работаем над добавлением новых языков в дальнейших раундах.

Желаем всем удачи на раунде и ждем на http://russiancodecup.ru!

UPD Выложен разбор задач

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

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

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

Всем привет и спасибо тем, кто принял участие в разогревочном раунде.

Во-первых, хотелось бы извиниться перед участниками за очереди тестирования в середине-конце первого часа. Для разогревочного раунда мы решили использовать задачи ИОИП — команда авторов задач RCC и ИОИП в целом совпадает — тур подготовили Виктория Ерохина (viktoria), Илья Збань (izban), Станислав Наумов (josdas), Михаил Путилин (SpyCheese), Андрей Станкевич (andrewzta), Дмитрий Филиппов (DimaPhil), Григорий Шовкопляс (GShark). Мы хотели дать возможность всем желающим познакомиться с довольно интересным, на наш взгляд, набором задач.

К сожалению, мы не учли один аспект. Можно заметить, что в RCC мы стараемся делать мультитест в задачах, ведь самая затратная операция — запустить решение участника на тесте. Поскольку ИОИП расчитывалась на меньшую нагрузку, в первой задаче оказалось 32 теста. Это не так много по меркам олимпиадной задачи, но когда в течение 15 минут 400 участников прислали правильное решение этой задачи, наших проверяющих мощностей не хватило.

Мы оперативно уменьшили число тестов в задаче и к середине контеста ситуация нормализовалась. Мы учтем этот эффект и будем тщательнее планировать формат тестов в задачах будущих контестов.

Перейдем теперь к разбору.

A. Космический корабль

Заметим, что если сила босса равна x, то сумма сил всех врагов равна 2x. Следовательно, чтобы найти силу босса, необходимо посчитать суммарную силу всех врагов и разделить ее на 2. После этого необходимо найти какого-нибудь врага с такой силой и поменять это значение в массиве f со значением последнего врага.

B. Рейнджеры в автобусе

Будем обрабатывать пассажиров по очереди и для каждого определять, кем он мог бы быть. Сразу заметим, что Розовый Рейнджер может есть куда угодно, поэтому каждый пассажир мог бы быть Розовым.

Для каждого места будем помнить, свободно ли оно. Для этого заведем unordered_set, в котором будем хранить занятые места. Проверим, мог бы очередной пассажир быть Красным Рейнджером. Для этого найдём место, на которое сел бы Красный. Переберём циклом ряд от 1 до n, если попался ряд, в котором есть свободное место, выбираем левое, если оно свободно, или правое, если нет — это и есть место, куда сел бы Красный. Поскольку он выбирает место однозначно, мы можем определить, мог бы этот пассажир быть Красным — нужно проверить, что он сел именно на это место. Таким же образом узнаем, куда сели бы Синий, Жёлтый и Чёрный – разница будет в порядке перебора рядов (от 1 до n или наоборот) и в порядке выбора места в ряду (сначала левое или правое). После обработки пассажира отметим его место как занятое.

Решение работает за O(nk) и использует O(k) памяти.

Чтобы улучшить решение, заметим, что каждый из циклов останавливается, когда находит ряд, в котором есть свободное место. Это означает, что нужно быстро находить первый и последний незанятые ряды. Будем поддерживать эти значения — first и last. Изначально first = 1, last = n. После каждой итерации цикла обновим эти значения. Будем увеличивать first на 1, пока ряд first занят, затем уменьшать last на 1, пока ряд last занят. Занятых рядов не может оказаться больше k / 2, поэтому first и last сделают O(k) шагов. Таким образом, улучшенное решение работает за O(k) и проходит все тесты.

C. Волшебное оружие

Предпочитаем массивы: R[a][b] — количество фрагментов у красного рейнджера, у которых первая цифра равна a, а последняя равна b; G[a] — количество фрагментов у зеленого рейнджера, у которых последняя цифра равна a; и B[b] — количество фрагментов у синего рейндждера, у которых первая цифра равна b.

Если бы у разных рейнджеров не было одинаковых моделей фрагментов, ответ был бы равен сумме по всем a и b значений G[aR[a][bB[b].

Чтобы учесть ситуацию, когда у какой-то пары совпадают модели, посчитаем количество пар фрагментов с одинаковым номером модели у красного и синего, красного и зеленого и синего и зеленого рейнджеров, соответственно (например, с помощью std::map). Заметим, что подходящими являются только номера, у которых первая и последняя цифра совпадает. Воспользуемся формулой включения-исключения, вычтем из ответа число таких пар, и добавим удвоенное количество троек фрагментов, когда у всех трех рейнджеров совпадают модели.

D. Рыцари и лжецы

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

Пусть dp[i][mask_prev][mask_cur] — максимальное количество рыцарей, которые могут быть в расстановке из первых i столбцов, mask_cur — маска i-го столбца, а mask_prev — (i - 1)-го.

Затем переберем маску для (i + 1)-го столбца и проверим выполняются ли ограничения на соседей, для солдат из i-го столбца, так как теперь известны все их соседи.

Для первого и последнего столбца ограничения нужно проверять отдельно, потому что у них нет одного из соседей.

База: dp[2][mask_prev][mask_cur] равно количеству единиц в mask_prev и mask_cur, если mask_prev может стоять слева от mask_cur.

Переход: dp[i + 1][mask_cur][mask_next] = dp[i][mask_prev][mask_cur] + ones(mask_next), где ones(x) — количество единиц в маске x.

Ответ будет максимумом среди всех dp[k][mask_prev][mask_cur], таких что mask_cur может находится справа от mask_prev.

Наконец заметим, что случай k = 1 стоит разобрать отдельно, поскольку в этом случае нет предыдущего столбца.

E. Параллелепипед

Расскажем об основной идее решения. Во-первых, рассмотрим отдельно все параллелепипеды, у которых две или три стороны совпадают, это можно сделать за O(n).

Пусть теперь все три стороны различны. Построим следующий неориентированный граф: вершинами будут возможные длины сторон, соединим ребром числа a и b, если имеется хотя бы два листа размером a × b. Задача свелась к рассмотрению треугольников в получившемся графе, это можно сделать за O(n2 / w) или O(nsqrt(n)) (здесь w обозначет размер машинного слва, 32 или 64, этот множитель возникает при битовом сжатии в поиске треугольников).

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

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

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

Всем привет!

Мы рады представить Russian Code Cup 2017! Отборочные раунды начнутся в апреле, а финальный раунд пройдет онлайн в сентябре. В каждом из трех квалификационных раундов в отборочный раунд выйдет по 200 лучших участников, а 50 лидеров отборочного раунда попадут в финал. Задачи будут предложены на русском и английском языках.

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

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

Удачи всем и до встречи на раундах!

UPD: Напоминаем, контест начнется в 14-00 по московскому времени. Задачи разогревочного раунда базируются на задачах заключительного этапа ИОИП, которую готовит та же команда авторов. Мы просим участников ИОИП поступить честно и не принимать участие в разогревочном раунде. Всем удачи!

UPD2: Спасибо всем участникам, которые устоили нам настоящее стресс-тестирование. К сожалению, в первый час контеста очереди были существенными, мы постарались решить эту проблему, и где-то с середины тура задержки уже не должны превышать 2-3 минут. Мы не допустим образования очередей в квалификационном и отборочном турах.

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

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

Автор 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 лет назад, По-русски

 

A. Контрольная работа

Переберем префикс заданий, которые решит Коля. Найдем время, которое он потратит, если будет сам решать все задания и найдем максимальное время, которое он потратит на одно задание в таком случае. Понятно, что если он будет списывать, то списывать надо именно самое долгое задание из тех, что он делает. Тогда время, которое Коля потратит равно S - max(0, M - t0). Теперь найдем наибольший префикс, у которого это время не превышает t.

B. Многоножка

Чтобы решить эту можно отсортировать все записи по количеству левых ног. Дальше будем перебирать количество левых ног по возрастанию. Если левых ног li, то потенциально могут быть верными, первые i записей, а все последующие записи точно не верны (За исключением количества ног равных li).

Теперь нужно узнать, сколько записей из первых i будут верны. Запись c номером j ≤ i верна, если rj ≤ n - li. Нахождение всех таких rj является стандартной задачей и решается, например, деревом отрезков.

Также нужно было обратить внимание, что сумма чисел в ответе должна быть равна n и корректно обрабатывать случай, где все записи могут быть неверны.

C. Двоичное дерево

Чтобы минимизировать количество дней, которые придется потратить Васе, нужно минимизировать высоту итогового бинарного дерева — если глубина листа в итоговом дереве равна h, то Вася потратит 2h - 1 - n дней на то, чтобы подвесить необходимое число вершин.

Заметим, что если в дереве есть вершина степени больше 3, то задание выполнить не получится — в полном бинарном дереве все вершины имеют не больше трех соседей. Обратим внимание, что корень в ответе имеет ровно двух сыновей.

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

D. Пробирки и реактивы

Приведем конструктивный алгоритм в несколько этапов:

  • Если sum(mini) > sum(vi) — ответ «NO»;
  • Если sum(maxi) < sum(vi) — ответ «NO»;
  • Нальем в каждую пробирку mini·pi / 100 миллилитров реактива ci;
  • После этого для каждого реактива i отсортируем пробирки, для которых cj = i по возрастанию pj и будем по очереди наливать в них оставшийся реактив номер i;
  • В каждую пробирку нальем еще (maxj - minjpj / 100 миллилитров i-го реактива или меньше, если он закончится;
  • Все оставшееся от реактивов разольем по пробиркам любым способом: в i-ю пробирку суммарно можно налить любое количество жидкости, не превосходящее summaryi·100 / pi (summaryi — суммарное количество жидкости в ней на текущий момент);
  • Если разлить не получилось и жидкость осталась — ответ «NO»;
  • Единственная оставшаяся проблема — в каких-то пробирках все еще может быть меньше mini миллилитров жидкости;
  • Для исправления этого в пробирке i, сначала перельем из всех остальных пробирок j реактивы, отличные от cj, в i-ю пробирку, так, чтобы в j-й пробирке осталось хотя бы minj жидкости;
  • Если и этого не хватило, перельем из остальных пробирок j жидкость номер cj (теперь это не испортит условие на процентное содержание).

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

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

E. Размен денег

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

Пусть на очередном шаге мы рассмотрели все монеты, стоимость которых не превышает X и мы можем заплатить любую сумму до Y включительно. Пусть суммарная стоимость монет, каждая из которых стоит больше X, но не больше Y + 1 равна sum. Тогда, если sum = 0, то Y + 1 мы не сможем представить с помощью этого набора. Иначе, перейдем к состоянию, что рассмотрены все монеты, стоимость которых не больше Y + 1, и мы можем представить любую сумму до Y + sum включительно.

Заметим, что стоимость максимальной монеты, которую мы рассмотрели растет как минимум также быстро как числа Фибоначчи. Значит, этот процесс завершится за O(log Answer) итераций.

Чтобы решить исходную задачу, необходимо научиться находить сумму чисел меньших X на отрезке. Если делать это за O(log n), то можно решать задачу суммарно за O(m log n log Answer).

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

F. Отборочный раунд, задача F

Ответ «NO» бывает только в одном случае, когда d не делится на наибольший делитель всех чисел ai. В любом другом случае ответ всегда существует. Для начала мы научимся получать хоть какой-то ответ без ограничения на значения, предварительно поделив все ai и d на gcd(a1, ..., an). Если n = 1, то x1 = d / a1. В других случаях, мы будем подбирать для каждого префикса [1, p] такие xi, p, что a1x1, p + ... + apxp, p = gcd(a1, ..., ap). Делается это индуктивным методом. x1, 1 = 1. Пусть мы уже нашли xi, p и хотим найти xi, p + 1. С помощью расширенного алгоритма Евклида мы находим s и t: s·gcd(a1, ..., ap) + t·ap + 1 = gcd(gcd(a1, ..., ap), ap + 1) = gcd(a1, ..., ap + 1). Тогда для i ≤ p xi, p + 1 = s·xi, p и xp + 1, p + 1 = t. Получив xi, n, вычисляем xi = d / gcd(a1, ..., anxi, n = d·xi, n. Такое решение работает за O(n2), что не подходит под ограничения. Чтобы сократить время работы, мы выберем среди ai подмножество, у которого наибольший общий делитель такой же как у всего множества и такое что нельзя уменьшить. Для этого мы будем перебирать от a1 до an и брать число в подмножество, если оно уменьшает количество различных простых делителей. Таким образом, выбранное подмножество содержит не более 7 элементов, так как максимальное количество простых у числа меньше либо равного 106 равно 7. Для чисел из подмножества мы запускаем вышеописанный алгоритм, а для чисел не вошедших в множество, мы просто выставляем xi = 0.

Теперь алгоритм работает за O(n), но не выполняются условия, что xi по модулю не превышают 106 и среди них не должно быть нулей. Для этого опишем процедуру нормализации. Сначала выполним для xi первое условие. В первую очередь, сделаем все xi для i > 1 неотрицательными и меньшими a1. Это делается простой операцией эквивалентного изменения: мы вычитаем из x1 kai и прибавляем к xi ka1, где k = (xi mod a1 - xi) / a1. Отметим, что результаты выполнения операции влезают в 64-битный тип данных, так как x1 по модулю не может превзойти |d - a2x2 - ... - anxn| < 106·106·105. Теперь пройдёмся по всем xi, начиная со второго до последнего, и с каждым будем последовательно производить операцию: вычесть a1 из xi и прибавить ai к x1. Заметим, что существует i, что после применения операции к x1, ..., xi получилось 0 ≥ x1 ≥  - 106. Пусть не существует такого i, тогда все xi стали отрицательными, чего случиться не могло, так как a1x1 + ... anxn даёт d, то есть положительное число. Научившись получать |xi| ≤ 106, осталось сделать их ненулевыми. Разобьём все нули на пары, возможно, кроме одного. В каждой паре i и j присвоим xi = aj и xj =  - ai. У нас, возможно, остался единственный индекс p, для которого xp = 0. Мы рассмотрим несколько случаев:

  • Если существует xj, которое по модулю не равно ap, тогда мы применяем операцию: вычитаем sign(xj)ap из xj и прибавляем sign(xj)aj к xp.
  • Если же такого xj не существует, но ap ≤ 5·105, тогда к любому xj можно применить операцию: прибавить sign(xj)ap к xj и вычесть sign(xj)ap из xp.
  • В противном случае должен найтись aq, такой что aq ≠ ap. Если такого не существует, то все ai = a1, а, значит, из-за нормировки на наибольший общий делитель ai = a1 = 1 и должен был выполниться второй случай. Мы проводим операцию: вычтем sign(xj)ap из xq и прибавим sign(xj)aqк xp. После этого xq равно нулю. Но теперь если n > 2 для q выполнится первый случай, а если n = 2, то для q выполнится второй случай, так как d = aqap ≤ 106.

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


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

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

Автор 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, 8 лет назад, По-русски

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, история, 8 лет назад, По-русски

Всем привет!

Мы рады представить окончательную версию правил 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, 8 лет назад, По-русски

A. Секретный код

Задача относится к типу «сделайте что просят». Очевидно, как посчитать количество совпадающих символов у двух строк. Для проверки встречался ли данный символ в исходной строке достаточно предподсчитать массив логических значений used[c], где хранится true если символ c встречался в секретном коде и false иначе.

B. Хаос

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

Ответ больше этого числа никак не получить, так как при замене двух чисел на две копии их среднего арифметического среднее арифметическое двух максимумов не увеличивается.

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

C. Новое приключение Марти и Дока

Требуется минимизировать сумму Sum(i = 1..n, j = 1..m, 2·ai, j·(|i - x| + |j - y| + 1)). Заметим, что нет слагаемых, зависящих от обеих коорданат, поэтому ее можно разбить на три: Sum(2·ai, j), Sum(2·ai, j·|i - x|) и Sum(2·ai, j·|j - y|).

Первая сумма — константа, вторую и третью можно минимизировать независимо, выбирая, соответственно, ряд и столбец. Исходя из вида второй суммы получаем, что оптимальный ряд — медиана набора чисел Sum(j = 1..m, a1, j) раз 1, Sum(j = 1..m, a2, j) раз 2, ..., Sum(j = 1..m, an, j) раз n.

Аналогично для второй координаты.

D. Разбиение на команды

Будем решать задачу методом динамического программирования.

Для начала отсортируем все числа и посчитаем, сколько раз каждое число встречается в массиве. Если в команде есть школьники с навыками l и r, то все школьники с силой m, где l < m < r, тоже обязательно будут в этой команде. Таким образом, можно решать задачу, считая значения dp[i][j][f] — количество способов разбить всех школьников с навыком не большим i на j команд, если в последнюю (одну из команд, в которой есть школьник силы i) команду мы f=true|false добавим или не добавим еще учеников. Переход — пусть у нас есть k школьников с силой i + 1. Тогда переберем g — на сколько команд мы их разобьем, и, используя вспомогательное значение f[k][g] обновим значения dp[i + 1][j'][f']. Заметим, что время заполнения массива есть O(nk), поскольку общее число значений g для фиксированного j равно n.

Чтобы посчитать f[k][g] — количество способов разбить k школьников одинаковой силы на g команд, нужно посмотреть, в какой команде находится k-й школьник — либо в своей отдельной команде (f[k - 1][g - 1]), либо в одной из уже существовавших до него (f[k - 1][gg).

Итого, каждый из массивов f и dp заполняется за O(nk), поэтому все решение работает за O(nk).

E. Максимальная сумма

Отсортируем bj по убыванию. Будем добавлять в массив ai все числа, не меньше очередного bj. Таким образом в массиве ai будут содержаться только отрезки чисел, для которых выполнено ai ≥ bj. При добавлении очередного ai, на каждом вновь образованном отрезке нужно находить отрезок с максимальной суммой. Это является достаточно стандартной подзадачей, которую можно решить, например, деревом отрезков (см, например, обсуждение здесь http://codeforces.com/blog/entry/17780).

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

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

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

Всем привет!

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

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

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

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

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

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

Задача А. Сгибание ленточки.

Идея: Владимир Смыкалов.
Реализация: Дмитрий Филиппов.
Разбор: Дмитрий Филиппов.

В задаче дана полоска 1×2n, которую сначала n раз сложили ровно пополам, а потом развернули обратно. Складывания возможны двух способов: левую половину наложить на правую или правую половину наложить на левую. Требуется отвечать на запросы — по номеру сгиба узнать, ориентирован он вверх или вниз.

Научимся отвечать на запрос за O(log 2n), то есть за O(n). Суммарное количество запросов не превосходит 105, поэтому этого вполне достаточно. Будем эмулировать процесс сгибания для каждого запроса. Зная текущие длину ленточки и номер сгиба, легко понять, в какой половине этот номер находится, а затем, зная, в какую сторону произойдет сгиб пополам, можно найти и новое положение сгиба в укороченной в два раза ленточке. Для ответа на запрос одновременно с положением поддерживаем, в какую сторону сейчас ориентирован сгиб. Итого, получаем решение за O(qn), где q — суммарное количество запросов.

Задача B. Сбор монет.

Идея: Виталий Аксенов.
Реализация: Борис Минаев.
Разбор: Борис Минаев.

В задаче дана лента, разбитая на клетки, по которой ходит игрок. Каждую секунду в каждой клетке дополнительно появляется некоторое число монет, фиксированное для каждой клетки. Игрок за секунду может перейти в соседнюю клетку или остаться в текущей. Каждый раз когда игрок находится в некоторой клетке, он собирает все монеты, которые в ней находятся. Необходимо посчитать, какое максимальное число монет может собрать игрок за t секунд.

Заметим, что для каждой клетки важно знать только последний момент, когда игрок в ней находился. Рассмотрим путь игрока с конца. В каждый момент времени про некоторый непрерывный отрезок клеток уже известен последний момент их посещения, а для остальных клеток нет. Поэтому можно воспользоваться методом динамического программирования, состоянием которого будет отрезок посещенных клеток и текущее время. Кроме того необходимо хранить текущую позицию игрока. Заметим, что можно хранить только те позиции, когда игрок стоит на одном из концов отрезка посещенных клеток. Из каждого состояния существует не более чем два перехода — игрок может посетить клетку слева или справа от уже посещенных. Таким образом, время работы алгоритма составляет O(n2t).

Задача C. Топологическая сортировка и дети.

Идея: Артем Васильев.
Реализация: Виталий Аксёнов.
Разбор: Виталий Аксёнов.

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

Рассмотрим сначала алгоритм, а потом докажем его корректность. Вершины, для которых дан порядок, будем называть помеченными, остальные — непомеченными. Мы знаем какие числа не использовались в топологической сортировке, будем выставлять их по одному от больших к меньшим в соответствие вершинам. У нас есть очередное нерасставленное число. Первым делом удалим все уже помеченные стоки. Далее для каждого непомеченного стока узнаем максимальное значение в помеченной вершине, достижимой по обратным рёбрам. Для каждой вершины это число можно предподсчитать заранее: находим какую-либо топологическую сортировку графа и решаем задачу динамического программирования. В качестве соответствующей вершины нерасставленному числу выбираем любой сток, у которого предподсчитанное число наибольшее, и удаляем его из графа.

Предподсчёт чисел выполняется за O(V+E). Каждую итерацию нужно выполнять как можно быстрее, поэтому в каждой вершине мы храним количество оставшихся из неё исходящих рёбер. Когда мы удаляем сток, у всех вершин, из которых есть в него ребро, уменьшается исходящая степень, и если эта степень становится нулём, помещаем эту вершину в сет по предподсчитанному значению. Тем самым в нашем сете хранятся все стоки, и для выбора нужного нам достаточно взять самую последнюю вершину из сета. Каждая вершина кладётся в сет и вытаскивается ровно один раз. Итого время работы алгоритма: O(V·log(V) + E).

Теперь докажем корректность нашего алгоритма. Заметим, что нам достаточно доказать правильность первого шага алгоритма, далее используем метод математической индукции. Рассмотрим корректно заполненную топологическую сортировку p, предварительно выкинув все помеченные стоки. Далее рассмотрим сток s, который мы выбрали нашим алгоритмом для максимального числа, и сток, которому оно соответствует в топологической сортировке p. Если мы все числа в перестановке большие p(s) уменьшим на единицу, а выбранному нами стоку s выдадим максимальное число, перестановка останется корректной. Операция произошла корректно: мы сохранили свойство топологической сортировки для непомеченных вершин, а для помеченных вершин значение топологической сортировки не поменялось, так как по свойству выбора вершины s p(s) больше, чем значение топологической сортировки любой помеченной вершины.

Задача D. Правильный сад.

Идея: Артем Васильев.
Реализация: Артем Васильев.
Разбор: Артем Васильев.

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

Для начала докажем эквивалентное свойство: множество точек хорошее тогда и только тогда, когда для каждого угла такого прямоугольника существует точка на смежной с этим углом стороне. Очевидно, если выполняется это свойство, то выполняется и свойство, описанное в условии задачи. Однако, верно и следствие в противоположную сторону: рассмотрим произвольный прямоугольник с углами в точках A и B. По предположению, внутри или на границе этого прямоугольника существует другая точка из множества, назовем ее C. Если эта точка уже лежит на стороне, смежной с A, то наше утверждение верно. Иначе рассмотрим прямоугольник, построенный на точках A и C. Его площадь строго меньше, чем площадь исходного прямоугольника, а точек в множестве конечное число, значит когда-нибудь мы выберем точку, лежащую на стороне, смежной с A.

Сформулированное свойство помогает дойти до решения: для точки (xi, yi) обозначим за lefti такой максимальный x < xi, что в заданном наборе существует точка (x, yi). В случае, когда это значение не определено, считаем его равным равным минус бесконечности. Аналогичным образом введем величины righti, downi и upi. Рассмотрим область (lefti, righti) × (downi, upi). Если в этой области есть другая точка из заданного набора, то можно найти две точки, для которых построенный прямоугольник нарушает свойство. Стоит заметить, что подходит не любая точка из этой области. В качестве второй точки всегда подходит, к примеру, ближайшая по евклидовому или манхэттенскому расстоянию. Когда в этой области находится только точка (xi, yi), все прямоугольники содержат другую точку на стороне, смежной с ней. Таким образом, решение свелось к проверке n прямоугольников на наличие более одной точки внутри.

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

Задача E. Междуречье.

Идея: Виталий Аксёнв.
Реализация: Илья Збань.
Разбор: Илья Збань.

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

Заметим, что поскольку многоугольник выпуклый, а ломаные монотонны и не пересекаются, верен следующий факт: если многоугольник пересекает ломаные i, k, то для любого j, что i ≤ j ≤ k, этот многоугольник пересечет и j-ю ломаную. Используя этот факт, будем решать задачу жадно: найдем максимальное i1, такое, что существует многоугольник, пересекающий все ломаные с 1-й по i1-ю, добавим его к ответу, и продолжим: найдем максимальное i2 такое, что существует многоугольник, покрывающий ломаные с i1+1-й по i2-ю, и так далее. Число многоугольников, которые пришлось использовать, и будет ответом.

Научимся для ломаной ik находить значение ik+1. Это максимальный индекс, такой, что существует многоугольник, покрывающий ik+1-ю и ik+1-ю ломаную. Решим сначала более простую задачу: есть точка A и отрезок BC, нужно проверить, существует ли многоугольник, содержащий внутри себя и данную точку, и какие-то точки отрезка. Для этого построим сумму Минковского этого многоугольника и его самого же, инвертированного относительно (0, 0) (то есть, с противоположными по знаку координатами всех точек). Это какой-то новый многоугольник, назовем его P, с O(n) вершин, содержащий внутри себя точку (0, 0). Сдвинем его так, чтобы точка (0, 0) перешла в точку A, и проверим, что отрезок BC пересекается со сдвинутым многоугольником P — несложно убедиться, что это необходимое и достаточное условие.

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

Пусть k — суммарное число вершин на всех ломаных. Найдём асимптотику используемых нами операций — O(n) на построение многоугольника P, O(n) на построение суммы Минковского P и отрезка ломаной и O(log n) на проверку пересечения выпуклого многоугольника и отрезка. Так как многоугольников нужно построить максимум k, а пересечь в худшем случае нужно каждый многоугольник с каждым отрезком, итоговая асимптотика времени работы равна O(nk + k2log n).

Задача F. Робот на дереве.

Идея: Борис Минаев.
Реализация: Борис Минаев.
Разбор: Борис Минаев.

В задаче дано неориентированное дерево из n ≤ 10 вершин. Про каждое ребро известна его прочность wi, которая не превышает 15. По дереву перемещается робот, который изначально находится в случайной вершине. Каждый раз робот равновероятно выбирает ребро из тех, по которым он может пройти, и перемещается по нему. Каждый раз когда робот проходит по ребру, его прочность уменьшается на единицу. Если прочность становится равна нулю, то ребро удаляется. Если у робота нет ребер, по которым он может пойти, он останавливается. Необходимо посчитать математическое ожидание длины пути робота.

Для начала решим более простую задачу. Предположим, что необходимо посчитать не математическое ожидание длины пути, а количество различных путей, по которым мог пройти робот. Представим, что мы зафиксировали вершины, в которых робот начал и закончил свой путь, а также количество раз, которые робот прошел по каждому ребру. Рассмотрим, какие существуют ограничения на эти количества. Во-первых, ребра, по которым робот прошел положительное число раз, должны образовывать связный подграф исходного дерева. Во-вторых, рассмотрим некоторую вершину и количество раз, которые робот прошел по всем ребрам, которые являются смежными с этой вершиной. Если эта вершина является концом пути, то каждое смежное с ней ребро должно использоваться ровно wi раз. Максимум два ребра смежных с вершиной должны быть использованы нечетное число раз. Причем, если эта вершина лежит на пути из стартовой в конечную, то ровно два ребра должны быть использованы нечетное число раз. Для стартовой и конечной вершины (если они не совпадают) должно быть ровно одно ребро, которое используется нечетное количество раз, либо ноль, если вершины совпадают. Для всех остальных вершин все смежные ребра должны быть использованы четное число раз.

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

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

Вернемся к первоначальной задаче. Теперь вместо количества способов будем хранить вероятность выбрать такой путь, а также математическое ожидание его длины. Подсчет вероятности отличается от подсчета количества способов только тем, что при каждом переходе по ребру необходимо поделить значение на количество ребер, которые инцидентны данной вершине. Единственная проблема, которая возникает при этом заключается в том, что степень вершины не является константой, так как ребра удаляются во время путешествия робота. Чтобы посчитать значения динамического программирования для ребра воспользуемся следующей идеей. Зафиксируем все поддеревья, ребра в которые будут удалены, а также общее количество раз, которое робот будет идти из вершины по некоторому ребру. Будем восстанавливать путь с конца. Существует два возможных варианта, которые необходимо различать. Либо робот идет по ребру, которое не будет удалено, тогда нужно уменьшить количество ребер, по которым еще необходимо пойти роботу и перейти к меньшей задаче. Либо робот идет по ребру, которое будет удалено, тогда к общему количеству ребер, по которым роботу необходимо пройти, нужно добавить количество раз, которые он пройдет по данному ребру, а также увеличить количество существующих инцидентных ребер на единицу.

Всего существует O(2childrenwi) состояний в динамическом программировании для заданного ребра, и переход между ними осуществляется за O(1). Так как необходимо посчитать значение динамического программирования для каждого количества раз, в которых было использовано ребро, общее время работы алгоритма получается равным O(2n(∑wi)2).

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

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

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

Всем привет!

Финал Russian Code Cup в этом году пройдет онлайн, но финалисты могут по желанию приехать лично на одну из двух площадок на выбор: офис Mail.Ru Group в Москве или в Университет ИТМО в Санкт-Петербурге.

В то время как участники будут решать выданные задания, с 11:30 по московскому времени на сайте https://it.mail.ru/rcc в прямом эфире будет транслироваться ток-шоу, посвящённое прошлому, настоящему и будущему программирования и высоких технологий.

Среди гостей такие известные люди как Николай Никифоров — министр связи и массовых коммуникаций РФ, Наталья Касперская — генеральный директор InfoWatch, Сергей Андреев — президент ABBYY, Болтунов Олег — член PostgreSQL Foundation, Алексей Пажитнов — изобретатель игры «Тетрис» и многие другие.

Гости будут обмениваться мнениями по самым разным вопросам, связанным с развитием IT и программирования. В качестве ведущих выступят Антон Комолов и Михаил Мирзаянов, руководитель Центра олимпиадной подготовки программистов Саратовского Государственного Университета.

Трансляция пройдёт 19 сентября с 11.30 до 16.00 московского времени на https://it.mail.ru/rcc

Среди зрителей будут разыгываться призы, так что приглашаем всех присоединиться к трансляции Russian Code Cup, а финалистам желаем удачи!

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

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