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

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

Представляю полный разбор задач Codeforces Round #153. Если возникнут какие-то вопросы или пожелания -- пишите в комментариях, постараюсь ответить.

252A - Маленький xor (A div 2)

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

252B - Разупорядочивание массива (B div 2)

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

Кажется, что этот алгоритм работает за O(N3). На самом деле это не так и его время работы можно оценить как O(N). Дело в том, что если есть хотя бы 3 пары позиций с различными числами, то одна из них обязательно подойдет. Это связано с тем фактом, что всего существует 2 различных отсортированных массива (по убыванию и по возрастанию), а все пары позиций с различными числами после обмена дают различные массивы. Следовательно, один из трех различных массивов обязательно будет не отсортированным.

252C - Точки на прямой (C div 2)

251A - Точки на прямой (A div 1)

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

252D - Игра с перестановками (D div 2)

251B - Игра с перестановками (B div 1)

В начале проверим, совпадает ли перестановка s с тождественной. Если совпадает, то ответ "NO".

Теперь приведем алгоритм, который работает во всех случаях, кроме одного. О нем будет сказано позже. Будем применять прямую перестановку (в условии она обозначается как q) до тех пор, пока текущая перестановка не совпадет с s, либо пока мы не сделаем k шагов. Если текущая перестановка совпала с s, то посмотрим на количество ходов, которые мы уже сделали (обозначим его через t). Если число k - t четное, то мы можем выбрать любую перестановку в нашей последовательности, кроме предпоследней и применять к ней (k - t) / 2 раз связку "обратная перестановка + прямая перестановка". Это можно не выполнять явно, просто достаточно проверить четность числа k - t. Итак, если оно четное, то ответ "YES".

Аналогично, попробуем применять обратную к q перестановку до тех пор, пока мы не получим перестановку s, либо пока не сделаем k шагов. И опять, если мы получили перестановку s, то проверим четность оставшегося количества ходов и в случае, если это количество четно -- выведем "YES". Можно понять, что в противном случае ответ "NO".

Приведенный выше алгоритм работает для всех случаев, кроме одного: когда перестановка q совпадает со своей обратной перестановкой, причем перестановка s достижима за один ход. В этом случае, если k = 1, то ответ "YES", иначе ответ "NO".

Полученный алгоритм работает за время O(N2).

252E - Превращение числа (E div 2)

251C - Превращение числа (C div 1)

Обозначим через L наименьшее общее кратное всех чисел от 2 до k. Заметим, что если a кратно L, то мы не сможем уменьшить его операцией второго типа. А это значит, что в оптимальной последовательности превращений будут присутствовать все числа, кратные L, которые находятся между b и a. Разобьем все числа от b до a на отрезки между числами, кратными L. Может выйти так, что первый и последний отрезки будут не полными. Видно, что достаточно решить задачу для первого и последнего отрезка, а также решить задачу для любого отрезка между ними, после чего последний результат нужно еще умножить на количество отрезков между крайними. Осталось только сложить полученные 3 числа и вывести ответ.

Также требовалось аккуратно рассмотреть случаи, когда у нас есть всего 1 или 2 отрезка.

Сложность решения O(L).

251D - Два множества (D div 1)

Обозначим xor всех чисел через X. Также, обозначим xor всех чисел в искомом первом наборе через X1, а во втором наборе -- через X2. Заметим, что если i-й бит в числе X равен единице, то i-е биты в числах X1 и X2 равны либо 0 и 1, либо 1 и 0, соответственно. Аналогично, если i-й бит в числе X равен нулю, то i-е биты в числах X1 и X2 равны 1 и 1, либо 0 и 0, соответственно. Как видим, на сумму X1 + X2 влияют только те биты, которые в числе X равны нулю. Пока забудем про то, что от нас требуется минимизировать число X1 и найдем максимальную возможную сумму X1 + X2.

Для того чтобы найти X1 + X2 сделаем еще одно наблюдение. Рассмотрим старший бит числа X, который равен нулю. Если существуют такие разбиения исходного набора на два, в котором этот бит равен единице в числе X1, то оптимальное разбиение обязательно должно быть одним из них. Чтобы доказать это, вспомним что соответствующий бит в числе X2 также равен единице. Обозначим соответствующую этому биту степень двойки через L. Тогда, если в числах X1 и X2 этот бит равен единице, то даже в том случае, когда все остальные биты равны нулю, сумма X1 + X2 равна 2L + 1. Если же в рассматриваемый бит мы в X1 и X2 поставим ноли, то даже в случае, когда все остальные биты равны единице, сумма X1 + X2 будет равна 2L + 1 - 2, что меньше, чем 2L + 1. Утверждение доказано.

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

Итак, для набора условий осталось научиться проверять, существует ли хотя бы одно разбиение, которое удовлетворяет всем этим условиям. Для каждого условия на бит с номером i составим уравнение над полем Z2 с n неизвестными, в котором коэффициент при каждой переменной равен i-му битчисла с соответствующим ей номером. Если неизвестная равна единице, то соответствующее число мы берем в первое множество, иначе -- во второе. Полученную систему уравнений можно решить алгоритмом Гаусса. Заметим, что нам не нужно каждый раз при добавлении нового уравнения решать всю систему с нуля. Достаточно лишь за O(NK) пересчитать матрицу из предыдущего состояния. Здесь, K -- количество уравнений в системе.

Итак, мы научились находить разбиение с максимальной суммой X1 + X2. Минимизировать X1 можно аналогичным образом: будем идти по всем битам, которые равны единице в числе X, начиная со старшего. Для очередного бита будем пытаться поставить в число X1 ноль. Если после этого система уравнений перестала иметь решение, то в соответствующем бите ставим единицу, иначе -- оставляем ноль и переходим дальше.

Сложность полученного алгоритма равна O(NL2), где L -- битовая длина максимального из чисел. Для дальнейшего ускорения можно использовать структуру bitset в алгоритме Гаусса, хотя этого не требовалось для получения Accepted на контесте.

251E - Дерево и таблица (E div 1)

Если N = 1, то существует ровно 2 укладки дерева на таблицу.

Если в дереве есть вершина, степень которой превышает 3, то ответ 0. Это связано с тем, что любая клетка таблицы имеет не более трех соседей.

Если в дереве нет вершин степени 3, то ответ 2 * n2 - 2 * n + 4. Эта формула выводится естественным образом при дальнейшем решении задачи. Также, можно было написать простую динамику для решения задачи в случае, когда у всех вершин степень 1 или 2. Так или иначе, давайте докажем приведенную формулу.

Для начала, решим немного другую задачу, которая будет использоваться при решении основного случая. Найдем количество способов уложить дерево на таблицу при условии, что в дереве нет вершин степени 3, а также одна вершина степени 1 прикреплена к левому верхнему углу таблицы (будем считать, что это вершина номер 1). Можно показать, что если размер таблицы 2xK, то количество способов укладки дерева равно K. Доказывать это будем по индукции. Для K = 1 утверждение, очевидно, выполняется. Для K > 1 будем доказывать следующим образом. Предположим, что таблица ориентирована горизонтально, т.е. у нее 2 строки и K столбцов. Если вершину, соседнюю с первой, мы поставим правее от нее, то потом у нас есть только один способ расположения дерева. Если же мы поставим ее снизу от первой, то следующую вершину мы обязаны поставить правее от нее, т.е. в клетку (2, 2). Теперь, мы получили ту же задачу, только K стало на единицу меньше. Таким образом, мы получили рекуррентное соотношение: f(K) = f(K - 1) + 1 и начальное условие f(1) = 1. Легко видеть, что в этом случае f(K) = K.

Итак, теперь вернемся к исходной задаче: сколько существует способов расположить дерево без вершин степени 3 на таблице 2xN. Без ограничения общности, предположим, что первая вершина дерева имеет степень 1. Будем рассматривать только те варианты, в которых первая вершина лежит на верхней строке таблицы, после чего просто умножим ответ на 2. Итак, если первая вершина лежит в первом или последнем столбце, то, как мы уже выяснили, существует N способов расположить на таблице оставшееся дерево. Если же первая вершина лежит в столбце с номером i (нумерация начинается с единицы, левый столбец имеет номер 1), то существует i - 1 способ, в котором соседняя с первой вершина лежит справа от нее. Также, существует N - i способов, в которых соседняя с первой вершина лежит слева от нее. Просуммировав это для всех столбцов и умножив ответ на 2, получим окончательный результат: 2 * n2 - 2 * n + 4.

Итак, остался самый главный случай, когда в дереве есть вершина степени 3. Объявим такую вершину корнем и подвесим дерево за него. Если существует несколько вершин степени 3, то в качестве корня мы можем выбрать любую из них. Предположим, что корень лежит в первой строке таблицы, а потом просто умножим ответ на 2. Ясно, что корень будет лежать на клетке, имеющей трех соседей. Переберем, какой из потомков корня будет лежать слева от него, какой будет лежать снизу, а какой -- справа. В случае, если потомок, лежащий снизу от корня, имеет степень 2 или 3, нам нужно еще перебрать, какой из его детей пойдет влево, а какой -- вправо. Всего есть не более 12 вариантов расположения потомков корня и потомков его "нижнего" сына. Итак, теперь у нас занят весь столбец, в котором лежит корень, а значит, как бы мы ни укладывали дерево дальше, все вершины, лежащие справа от корня там и останутся лежать. Аналогично, все вершины, лежащие слева от корня, там и останутся. Мало того, у нас есть фиксированное количество вершин слева и справа от корня, а значит, у нас есть ровно один вариант его расположения на таблице. Заметим, что если слева от корня находится нечетное количество вершин, то дальше дерево мы уложить не сможем. Посчитать количество вершин можно, просто сложив размеры поддеревьев для его потомка, а также потомка его "нижнего" сына, которые находятся слева от корня.

Итак, у нас есть 2 независимые подзадачи и нам нужно для каждой из них посчитать количество способов уложить дерево в таблице. При этом, у нас возможны только 2 ситуации:

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

2) Нам нужно уложить поддеревья с корнями в вершинах v1 и v2 на прямоугольную таблицу, причем вершина v1 находится в левом верхнем углу таблицы, а вершина v2 -- в левом нижнем.

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

Покажем, как свести задачу второго типа к задаче первого типа. Итак, если у вершины v1 или v2 есть 2 потомка, то ответ 0. Если у них по одному потомку, то мы можем перейти в них и получить ту же самую задачу. Если у обоих вершин нет потомков, то мы уже покрыли всю таблицу, так что ответ на задачу равен 1. Если же у одной вершины нет потомка, а у второй вершины один потомок, то мы получили задачу первого типа для этого потомка.

Итак, нам осталось решить задачу первого типа. Обозначим через f(v) количество способов уложить поддерево с корнем в вершине v на прямоугольную таблицу. Размер этой таблицы однозначно определяется из размера поддерева. Для решения задачи нам нужно рассмотреть 2 случая:

а) Вершина v имеет степень 2.

б) Вершина v имеет степень 3.

В случае, если вершина v имеет степень 2 и в ее поддереве нет ни одной вершины степени 3, то f(v) = s(v) / 2, где s(v) -- размер поддерева с корнем в вершине v. Ранее мы уже доказывали эту формулу. Теперь, предположим что в поддереве вершины v есть хотя бы одна вершина степени 3. Если таких вершин несколько, выберем ту, которая ближе всего к v. Обозначим ее через w. Итак, есть 2 варианта:

а.1) Путь из вершины v в вершину w пойдет так, что предпоследняя вершина в пути лежит слева от w.

а.2) Путь из вершині v в вершину w пойдет так, что предпоследняя вершина в пути лежит сверху от w.

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

В каждом из вариантов а.1) и а.2) переберем, куда идут потомки вершины w (одно направление уже занято ее предком, так что остается ровно 2 свободных направления). В случае, если потомок степени >1 идет вниз, требуется также перебрать, куда идут какие его потомки (есть 2 варианта: вправо и влево). После этого, справа от вершины w опять получается задача типа 1) или 2), которые были сформулированы выше. Слева от вершины w у нас есть дерево либо не укладывается вообще, либо укладывается однозначно. Это можно проверить исходя из длины пути из v в w, а также размера поддерева внука вершины w, который пошел в тот же столбец, что и w (если такой внук вообще есть). Осталось просуммировать ответы для всех этих вариантов.

Итак, задачу типа а), когда вершина v имеет степень 2, мы решать уже умеем. Осталось решить задачу б), когда вершина v имеет степень 3. Для этого достаточно перебрать, какой из ее потомков пойдет вправо, а какой — вниз. После этого получаем задачу либо типа 1), либо типа 2), которые были сформулированы выше.

Итоговая сложнось решения O(N).

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

Разбор задач Codeforces Round 153 (Div. 1)
Разбор задач Codeforces Round 153 (Div. 2)
  • Проголосовать: нравится
  • +34
  • Проголосовать: не нравится

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

Всем привет!

В четверг, 6 декабря в 19:30 MSK состоится Codeforces Round #153, автором которого являюсь я. Это уже третий мой раунд на Codeforces и я надеюсь, что будут еще.

Спасибо Shtrix, Seyaua и sdya за помощь в тестировании задач, а также Gerald за помощь в подготовке раунда. Отдельное спасибо Delinur за перевод условий на английский.

Надеюсь, задачи вам понравятся.

Всем удачи!

UPD: Опубликован разбор задач.

Поздравляем победителей!

Division 1:

  1. Egor
  2. tourist
  3. rng_58
  4. kelvin
  5. Burunduk1

Division 2:

  1. inker
  2. WhoTheHellIsMe
  3. memo1288

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

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

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

Всем привет!

Я добавил в Тренировки два контеста из летних сборов в Петрозаводске 2010 и 2011 годов.

Первый контест готовили студенты Киевского национального университета: Владислав Симоненко, Роман Ризванов и я (Ярослав Твердохлеб). Второй контест совместно готовили студенты Киевского и Харьковского национальных университетов: Андрей Коротков (КНУ), Степан Паламарчук (КНУ), Владислав Симоненко (КНУ), Дмитрий Соболев (ХНУ), Евгений Соболев (ХНУ) и я (Ярослав Твердохлеб — КНУ).

Надеюсь, контесты вам понравятся. Удачи!

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

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

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

Представляю разбор задач Codeforces Beta Round #97. Если есть какие-то вопросы или пожелания --- пишите в комментариях.

136A - Подарки (A Div 2)


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

Сложность решения O(N).

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

Разбор задач Codeforces Beta Round 97 (Div. 1)
Разбор задач Codeforces Beta Round 97 (Div. 2)
  • Проголосовать: нравится
  • +87
  • Проголосовать: не нравится

Автор KADR, 12 лет назад, По-русски
Всем привет!

В пятницу, 9 декабря в 19:00 MSK состоится Codeforces Beta Round #97, автором которого являюсь я. Это мой второй полноценный раунд на Codeforces и надеюсь, что не последний :)

Спасибо maksay, Shtrix, it4.kp, RAD и Delinur за помощь в подготовке раунда, тестировании задач и переводе условий.

Удачи на раунде!

UPD: По техническим причинам раунд переносится на 5 минут вперед.

UPD 2: По причине большого числа участников и большого количества тестов, результаты появятся не скоро.

UPD 3: Тестирование завершено, результаты известны. Всем спасибо за участие! Приношу свои извинения за настолько длительное тестирование.

Победители:

Div 1
4. Shef
7. ania
9. NALP

Div 2

UPD 4: Опубликован разбор.

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

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

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

Завтра (01.05.2011) состоится личный этап кубка Векуа. На snarknews.info сказано, что он будет проходить по системе TCM/Time. Может кто-то знает, это то же самое что и TCM/SE или же что-то новое?

UPD: правила есть на сайте кубка Векуа.

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

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

Автор KADR, 13 лет назад, По-русски
Теперь разбор завершен.

Задача A: Подарок (Роман Едемский)

Предположим, что оптимальным решением является пара (A, B), где A - количество золотых монет в подарке, а B - количество серебряных монет.  Нетрудно заметить, что существуют такие 2 индекса i и j (возможно совпадающих), что gi = A и sj = B, так как в противном случае мы бы могли уменьшить A или B, не нарушив связность графа.

Обозначим через R(A,B) граф, в котором для всех i выполняется gi ≤ A и  si ≤ B.

Обозначим через T(A) взвешенный граф, в котором для всех ребер выполняется ограничение gi ≤ A, а весами ребер будут si. Найдем в этом графе остовное дерево, у которого максимальное ребро - минимальное возможное. Можно показать, что для данного фиксированного A наименьшим значением B, при котором граф R(A, B) все еще связный, как раз будет это значение максимального ребра.

Утверждение. Минимальный остов графа одновременно является остовом, у которого максимальное ребро - минимальное возможное.
Доказательство. Рассмотрим любой минимальный остов L. Если существует остов P, у которого все ребра имеют строго меньший вес, чем вес максимального ребра у L. Тогда мы могли бы удалить из L макс. ребро и заменить его каким-то ребром из P, тем самым уменьшив его вес. Поскольку L - минимальный остов, мы не можем уменьшить его вес, следовательно такого P не существует.

Отсортируем все ребра исходного графа по возрастанию gi. Переберем все возможные значения A среди gi. Тогда ребрами графа T(A) для фиксированного i будут все ребра с индексами j ≤ i. Осталось научиться быстро находить мин. остов в этом графе.

Предположим, что мы для какого-то i нашли мин. остов в графе T(gi) (в случае, если граф несвязный, то в каждой его компоненте связности найден мин. остов). Добавим в него i + 1-е ребро. Если это ребро соединяет разные компоненты связности, просто добавим его, в противном случае в дереве образуется ровно один цикл. Найдем в нем максимальное ребро и удалим его из графа, получив таким образом новый минимальный остов в компоненте, куда добавилось ребро (доказательство опускается).

Поиск максимального ребра в цикле на дереве можно осуществить за O(N), а весь алгоритм будет работать за O(NM + MlogM).

Задача B: Мыши и сыр (Роман Ризванов)

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

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

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

Сложность решения O(N + M).

Также существует решение этой задачи при помощи динамического программирования.

Задача C: Мутация (Ярослав Твердохлеб)

В разборе вместо терминов "риск", "геном" и "ген" будут использоваться термины "стоимость", "строка" и "символ". Обозначим начальную строку через S.

Обозначим через M битовую маску символов, которые будут удалены. Переберем все возможные M. Для фиксированного M найдем стоимость строки, которая получилась и если она не превышает T - увеличим ответ на 1. Реализация этого алгоритма "в лоб" работает за O(2KN).

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

Рассмотрим некоторую пару индексов символов из начальной строки l и r. Обозначим через M' битовую маску всех символов, которые лежат строго между ними. Если или , то, очевидно, эти позиции рядом никогда не будут. Отсюда можно сделать вывод, что для фиксированного l возможных положений для r может быть не больше, чем K. Значит, всего таких пар O(NK). Определим, какой вид должно иметь множество M, чтобы после его удаления эти позиции оказались рядом? Нетрудно заметить, что для этого должны выполняться такие 2 условия:

1.
2.

Переберем все допустимые пары l и r, для каждой найдем M'. Попутно для каждой тройки (a,b,P) будем хранить сумму стоимостей соседства всех пар l и r, для которых Sl = a, Sr = b, M' = P. После этого для фиксированного M переберем все пары символов (a, b), а так же подмножества P и просуммируем стоимости, которые мы сохранили. Прибавим к этому стоимость выбрасывания символов из M и получим конечную стоимость строки. Сложность решения O(3K * K2 + NK).

Попробуем улучшить предыдущее решение, засчет уменьшения множителя при 3K. Рассмотрим такой (неправильный) алгоритм:
Переберем все допустимые пары l и r и для каждой найдем M'. Заведем массив v, в котором для каждой маски P будем хранить сумму стоимостей соседства для всех пар l и r, для которых M' = P. Для фиксированного M найдем сумму значений из v по всем подмаскам M, прибавим к ней стоимость удаления M и получим конечную стоимость строки.

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

v[M'] +  = cost

При таком заполнении v описанный выше алгоритм будет работать правильно и сложность станет O(3K + NK).

Этого все еще недостаточно, но мы уже почти у цели. Осталось научиться быстро считать сумму в массиве v по всем подмаскам M. Для этого рассмотрим следующий итеративный алгоритм:

Пусть перед первой итерацией у нас есть массив v, в котором хранятся начальные значения. После итерации с номером i в v[mask] будет храниться сумма значений из изначального массива v по всем подмаскам mask, для которых первые K - i бит совпадают с соответствующими битами маски mask. На итерации с номером i для всех масок, в которых i-й бит (нумерация с единицы) единичный сделаем сделаем такое: v[mask] +  = v[mask\{i}]. Нетрудно заметить, что после выполнения K итераций этого алгоритма в новом массиве v как раз и будут находиться суммы по всем подмаскам из изначального массива v.

Сложность алгоритма O(2KK + NK).

Задача D: Плюс и XOR (Даниил Нейтер)

Рассмотрим какой-то единичный бит в X. Если соответствующий бит в Y равен 0, то мы можем их поменять местами, уменьшив X и увеличив Y. При этом их сумма и xor не изменятся. Отсюда можно сделать вывод, что если какой-то бит равен единице в X то он будет равен единице и в Y. Отсюда Y = X + B. Учитывая, что X + Y = X + X + B = A,  получаем следующие формулы для нахождения X и Y:

X = (A - B) / 2
Y = X + B

Следует также учесть, что если выполняется хотя бы один из следующих пунктов:
  •  A < B
  • A и B имеют разную четность
  • X and (A - X) ≠ X, где and - побитовое "и"
то ответа не существует и следует вывести -1.

Задача E: Точки (Даниил Нейтер)

Если перегруппировать слагаемые, то можно разбить сумму на две:


Рассмотрим подсчет суммы по иксам:


Полученная формула позволяет посчитать ответ за 1 проход. Сложность решения O(N).


Задача F: Турист (Илья Порублев)

Из события i можно попасть в событие j, если выполняются условия:
  • ti ≤ tj
  • |xi - xj| ≤ |ti - tjV
Если события представлять в виде точек на координатной плоскости с координатами (xi, ti), то предыдущие 2 условия можно представить графически, а именно:

Из события i можно попасть в событие j, если точка (xj, tj) лежит внутри угла направленного вверх с вершиной в (xi, ti), а стороны которого образуют угол arctg(V) с вертикалью. Сделаем преобразование координат, при котором точка с координатами (xi, ti) переходит в точку (pi, qi), где
pi =  - xi + ti * V
qi = xi + ti * V

Тогда условие того что из события i можно попасть в событие j принимает вид: pi ≤ pj и qi ≤ qj. Отсортируем все точки по возрастанию координаты p, а при равном p - по возрастанию q. Задача (та часть, где можно самому выбирать стартовую точку) сводится к тому, чтобы в получившемся массиве найти самую длинную возрастающую подпоследовательность по q. Это можно сделать за O(NlogN).

Чтобы решить часть, в которой турист стартует из точки (0, 0) можно создать фиктивное событие со временем 0 и абсциссой 0 (если такого еще небыло) и потребовать чтобы мы обязательно посетили его первым.

Суммарная сложность решения O(NlogN).

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

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

Автор KADR, 13 лет назад, По-русски
Добрый день!

Я хотел бы анонсировать неофициальную трансляцию всеукраинской олимпиады школьников по информатике, которая будет проведена на Codeforces во вторник, 12 апреля в 16:00 по московскому времени. Контест пройдет на задачах недавно завершившейся всеукраинской олимпиады по информатике - UOI2011. Правила будут ACM-ICPC. К участию будут допускаться как команды, так и индивидуальные участники. Рейтинг за контест начисляться не будет.

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

Авторами задач являются: Даниил Нейтер, Роман Едемский, Роман Ризванов, Илья Порублев и я (Ярослав Твердохлеб).

После начала контеста задачи будут доступны в PDF по ссылкам:

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

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

Автор KADR, 13 лет назад, По-русски
Я давно собирался написать некоторые свои мысли о системе взломов, которую мы имеем на данный момент, и раунд 60 стал своеобразным толчком к этому.

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

Мне всегда казалось, что на олимпиадах по программированию вроде Codeforces или Topcoder в первую очередь нужно уметь решать задачи, а уже потом взламывать чужие решения. Кто-то скажет что все заранее знают правила и у всех свои стратегии. Я согласен с этим, но тем не менее не считаю правильным иметь возможность за счет вариации стратегии будучи с 2-мя задчами обойти человека который решил 4, причем не очень медленно.

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

Раунд                                              Максимальный процент                        Средний процент
Codeforces Beta Round #39                            12.1%                                                    4.8%
Codeforces Beta Round #41                            10.3%                                                    7.0%     
Codeforces Beta Round #47                            15.4%                                                    5.2%
Codeforces Beta Round #48                            30.8%                                                    15.9%      
Codeforces Beta Round #50                            10.8%                                                    5.4%
Codeforces Beta Round #51                            11.6%                                                    2.8%
Codeforces Beta Round #53                            20.8%                                                    10.8%
Codeforces Beta Round #56                            12.2%                                                    2.4%
Codeforces Beta Round #58                            27.3%                                                    6.5% 
Codeforces Beta Round #60                            70.5%                                                    53.0%

Итак, в каждом из последних 10 контестов для обоих дивизионов был хотя бы один участник из первой пятерки, набравший более 10% своих баллов на взломах. В 4 из 10 контестов был хотя бы один участник из топ-5, набравший более 20% своих баллов на взломах. В одном контесте был участник, набравший более 70% своих баллов на взломах, причем в среднем на этом контесте первая пятрка набрала более 50% баллов на взломах.

Хоть ситуации подобные последнему раунду - редкость (хотя, в раунде 58 было бы то же самое, не будь там проблемы с условием задачи А), но все же они встречаются. Далее я постараюсь провести анализ ситуации со взломами на последнем раунде.

Итак, победитель раунда реализовал 39 успешных взломов, причем в его комнате другими участниками была сделана всего одна успешная попытка взлома. Теперь возьмем комнату 3. Во взломах в этой комнате принимали участие 4 человека, из них двое сделали более одного успешного взлома, поэтому далее будем учитывать только их. В сумме они реализовали 28 успешных взломов по задаче А, причем в этой комнате было всего 3 решения по этой задаче, которые не прошли финальное тестирование. Учитывая что в комнате победителя было 5 решений задачи А, которые не прошли финальное тестирование, то можно проигнорировать эти 3 решения из комнаты 3. Получаем, что даже если сильнейший участник попадет в комнату 3 и реализует эти 28 взломов с 1 попытки, он максимум сможет получить 2800 очков на взломах против 3800 реально полученных очков из комнаты 7.

Из-за распределения по комнатам очки за взломы могут варьироваться даже на +-1000 и даже сильнейшие участники попав в неудачную комнату не имеют шансов выиграть контест. Рассматривая эту ситуацию с комнатами 3 и 7 я не учитываю то что взламывать могут несколько человек в одной комнате, что тоже сильно снижает суммарные очки по взломам. Например, в комнате 6 два участника примерно в одно и то же время (на 11 и 13 минутах) начали взламывать задачи А, позже к ним присоединился еще один. Каждый из первых двух начавших взламывать реализовал по 12 взломов. Сильнейший участник попав в эту комнату не имеет шансов реализовать все взломы в этой комнате, даже если он очень быстро сдаст и заблокирует А и сразу начнет взламывать чужие решения. Даже если предположить что у него отберут 12 взломов, реализовав все остальные взломы с 1 раза он сможет набрать 2000 очков на взломах, что на 1800 меньше чем у победителя раунда. Опять же, шансов победить в этом раунде у него нет.

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

1. Разделить участников из 1 и 2 дивизиона в разные комнаты. В среднем первые 5 участников по турнирной таблице последнего раунда набрали +300 очков на взломах фиолетовых, оранжевых и красных, остальные очки были набраны на серых, зеленых и синих. Разделив дивизионы, красные уже не смогут так беспощадно взламывать серых, тем самым уменьшится суммарное количество очков по взломам.

2. Изменить количество очков за взломы. Я уже не помню кем и где высказывалась мысль о том, что можно насчитывать баллы за взлом в зависимости от разности рейтинга взломщика и взламываемого. Например, если красный взламывает серого, прибавлять 20 баллов, если же серый взламывает красного, прибавлять 100 баллов. Количество баллов за взломы у победителя не будет заоблачным, в то же время вряд ли серые смогут навзламывать много красных и выбиться в топ турнирной таблицы за счет этого, не решив при этому большую часть задач.

Это сугубо мое мнение и оно может отличаться от мнения общественности. Критика и комментарии приветствуются.

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

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

Автор KADR, 14 лет назад, По-русски
Предположим, что верхняя и нижняя стороны прямоугольника фиксированы и нам осталось только выбрать левую и правую стороны. Тогда мы за О(К) можем найти список всех прямоугольников, которые попадают в нашу полосу, а так же список всех прямоугольников, у которых только часть попадает в полосу. Тогда мы можем представить это в виде отсортированного набора отрезков [l,r], где l и r - икс координаты левой и правой сторон прямоугольника.

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

Таким образом, мы уже имеем решение за O(N2K): перебрать все возможные горизонтальные полосы и в каждой за О(К) посчитать количество прямоугольников, которые покрывают от 1 до 3 объектов. Заметим, что если, например, верхняя сторона полосы не прилегает ни к какому из объектов, то мы можем ее сдвинуть вверх либо вниз и ответ для полосы не изменится. Следовательно, можно перебирать в качестве границ полосы только те строки, в которых есть хотя бы одна клетка, принадлежащая объекту, а затем домножать полученный ответ на расстояния до ближайших "не пустых" строк снизу и сверху.

Таким образом, мы получили решение работающее за O(K3) и не зависящее ни от размеров поля, ни от ограничения на максимальное количество покрываемых объектов.

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

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

Автор KADR, 14 лет назад, По-русски
На топкодере регулярно проходит shortest code competition по задачам срмов (обычно простым). Иногда довольно интересно посмотреть, как можно сжать код, казалось бы простой задачи, до совершенно непонятного, но в то же время работающего. Предлагаю проводить такие соревнования и здесь, на Codeforces.

Я начну с задачи C из Codeforces Beta Round #24. Правила простые: каждый следующий код должен быть короче предыдущего. Побеждает тот, кого никто не смог перебить. Длина кода - количество символов в нем, не считая пустых (пробелов, табов, переводов строки и т.д.). Само собой, код должен быть АС хотя бы на одном из доступных компиляторов. Насколько я знаю, на топкодере дефайны в С++ не используют в таких соревнованиях, поэтому предлагаю и здесь их не использовать.

Учитывая возможности некоторых языков, предлагаю проводить отдельный зачет по разным языкам, т.к. вряд ли C++ или Pascal смогут соревноваться с Haskell или Python.

214:
#include <iostream>
__int64 n,k,x,y,a['   '],b['   '],d;
int main()
{
std::cin>>n>>k>>x>>y;
for(k%=2*n;d<n;d++) std::cin>>a[d]>>b[d],d<k?x=2*a[d]-x,y=2*b[d]-y:0;
for (d=0,k-=n;k>0;--k,++d) x=2*a[d]-x,y=2*b[d]-y;
std::cout<<x<<" "<<y;
}

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

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

Автор KADR, 14 лет назад, По-русски
Представляю разбор Codeforces Beta Round #13. Если будут какие-то вопросы или замечания - прошу писать в комментариях.

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

Разбор задач Codeforces Beta Round 13
  • Проголосовать: нравится
  • +27
  • Проголосовать: не нравится

Автор KADR, 14 лет назад, По-русски
Приветствую всех на Codeforces Beta Round #13, который состоится в четверг, 6 мая в 18:00 по московскому времени. Автором задач этого контеста буду я. 
Хочется сказать отдельное спасибо Михаилу Мирзаянову, который сделал проведение контеста возможным, Роману Едемскому и Андрею Максаю за помощь в тестировании авторских решений, а так же Дмитрию Матову за перевод условий на английский язык. Надеюсь, задачи вам понравятся.

Желаю чтобы число 13 оказалось для вас счастливым!

UPD: Поздравляю Ивана Метельского, который стал победителем решив все 5 задач!
Задачи можно посмотреть тут.
Полные результаты можно посмотреть тут.

UPD2: добавлен разбор.

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

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