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

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

Добрый вечер, друзья!

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

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

Мы — это авторы задач yaro и Rei, куратор раундов Codeforces Gerald и автор системы MikeMirzayanov. Отдельное спасибо Паше (PavelKunyavskiy) и Артему (RAD) за тестирование и дельные замечания.

Последний раз, когда я участвовал в проведении соревнования на Codeforces, раунды еще носили статус "beta". Но чем меньше "beta", тем больше ответственность. В связи с этим желаю организаторам и авторам еще одного успешно проведенного раунда. Участникам же — нестандартных задач и идей, чистого кода (а также чистой клавиатуры) и побольше правильно сданных решений!

Нам представляется непростой задачей оценить уровень сложности задач для всей массы участников, поэтому разбалловка будет динамическая. Все же ради любопытства сделаем следующее предположение об относительной сложности задач: div.1: B-B-C-C-E, div.2: A-B-C-C-E. Угадаем ли?

UPD Приносим извинения за проблемы с очередью тестирования Codeforces.

В любом случае, нам будет очень приятно, если вы оцените наш раунд (после его завершения): short survey.

В упорной борьбе с отрывом в один взлом победителем div.1 стал meret (Jakub Pachocki)!

Доступны результаты первого дивизиона, результаты второго дивизиона.

Разбор.

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

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

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

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

Возьмем, к примеру, ключевой момент кода — переход динамики. Один из этих вариантов (угадайте, какой) прошел вчера претесты, но этим дело и ограничилось — получил вердикт WA (по точности) на каком-то объемном тесте.

А теперь вопрос: как вы думаете, какие из этих вариантов получают ОК по задаче? Почему?

Версия 1:

for (int j = 0; j < p[u].length; j++) {
	double np = (1 - (double) j / a[u]) * p[u][j];
	if (j < p[u].length - 1) {
		np += (double) (j + 1) / a[u] * p[u][j + 1];
	}
	p[u][j] = np;
}

Версия 2:

double frac = (double) 1 / a[u];
for (int j = 0; j < p[u].length; j++) {
	double np = (1 - j * frac) * p[u][j];
	if (j < p[u].length - 1) {
		np += (j + 1) * frac * p[u][j + 1];
	}
	p[u][j] = np;
}

Версия 3:

for (int j = 0; j < p[u].length; j++) {
	double np = (double) (a[u] - j) / a[u] * p[u][j];
	if (j < p[u].length - 1) {
		np += (double) (j + 1) / a[u] * p[u][j + 1];
	}
	p[u][j] = np;
}

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

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

Автор yaro, 14 лет назад, По-русски
А. Зеркальные числа.
Во-первых, заметим, что если интервал содержит число, состоящее из a цифр, а также число из b цифр (где a > b), то нет смысла рассматривать число из b цифр (так как вес числа 10k больше весов чисел, меньших 10k).
Если же рассмотреть числа с фиксированным числом цифр (s + 1), то для них сумма числа и его отражения — константа. Таким образом, учитывая что произведение чисел с фиксированной суммой растет при их сближении, картина у нас следующая: вес числа растет от 10s до 5· 10s -  1, затем он такой же у 5· 10s, затем он убывает и достигает своего минимального значения в 10s + 1 - 1.
Это доказательство, решение же такое: максимальный вес достигается либо в l, либо в r, либо в числах вида 5· 10s (можно, например, проверить все s, такие, что 5· 10s лежит в интервале).


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

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

Последнее, что осталось сделать — раскрасить наши фигурки указанным в условии образом. Это можно сделать, например так: раскрасим всю плоскость в 9 цветов, клетку (i ,j) в цвет (i % 3) * 3 + j % 3 (красим 3 × 3 в 9 цветов и замощаем им плоскость). Затем красим каждую доминошку в цвет ее черной (при шахматном раскрашивании) клетки. Каждую фигурку красим в цвет
доминошки вокруг которой она образована.

2. Жадность.
Будем снова пробегать массив содержащий поле слева-направо, сверху-вниз и жадно подбирать для каждой все еще свободной клетки фигурку. При этом будем выбирать ее так, чтобы никакая из свободных клеток, после этого не становилась изолированной. А именно будем докидывать к нашей фигуре каждую клетку, которая оказывается изолированной. Сделав это в правильном порядке получим фигурку из не более 5 клеток.

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


C. Генная инженерия.
Предположим, мы построили строку wp длины k < n и хотим узнать, сколько существует способов продолжить эту строку до строки длины n, удовлетворяющей условию фильтрации.
Какие параметры нам понадобятся, чтобы строить строку дальше?
Во-первых, нам необходимо знать, до какого места строка уже покрыта (обозначим этот индекс ci и назовем индексом покрытия). Кроме того, на необоходима какая-то информация о том, что находится на конце нашей строки.
Тут на помощь приходит довольно общеизвестная идея: найдем все префиксы ALLP строк \{si\} и добавим к текущему состоянию следующий параметр: наибольший префикс , такой что wp оканчивается строкой pk (в качестве альтернативы, можно рассматривать вершину бора, построенного по строкам из коллекции).
Теперь попробуем понять, почему этого достаточно. Во-первых, когда мы дописываем к wp какой-либо символ c мы можем легко найти pk + 1. pk + 1 будет равно наибольшей строке из ALLP, такой, что pk + c оканчивается на нее (мы можем предподсчитать все такие переходы заранее). Таким образом, единственная вещь, которую нам осталось научиться пересчитывать (чтобы использовать динамическое программирование) — это индекс покрытия. Но мы заметим, что он может измениться лишь какой-либо строкой коллекции, которая стоит на конце wp + c. Теперь заметим, что достаточно рассмотреть только самую длинную из таких строк (обозначим ее fs), и что fs обязательно содержится в pk + 1 (и поэтому тоже легко предвычисляется для всех строк из ALLP). Далее замечаем, что новый индекс покрытия зависит только он длины fs: он либо совпадает со старым, если fs не накрываем символ ci + 1, либо становится равным k + 1, если fs накрывает этот символ.

После вычисления всех величин d[k][k-индекс покрытия][макс. префикс] требуемое количество строк равно сумме d[n][0][mp] по всем префиксам mp.


D. Мощный массив.
Будем решать задачу оффлайн за время O(n sqrt(t)) и O(n) памяти. Для простоты рассуждений будем считать, что запросов n.

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

Обход запросов в произвольном порядке, разумеется, обойдется нам в O(nt) шагов.
Пусть p = sqrt(n). Разобьем массив на p равных частей Q_1, ... Q_p. Теперь рассмотрим запросы вида (Q_i, Q_j) по всем i и j (их будет приблизительно n). Очевидно, никакой порядок обхода не даст нам количество шагов, меньшее O(n sqrt(n)), так как на переход от одного интервала к другому в
любом случае потребуется хотя бы sqrt(n) шагов.

Тем не менее, порядок обхода, гарантирующий O(n sqrt(n)) ходов в худшем случае, существует.
Отсортируем интервалы запросов по следующему правилу: сначала идут те, левый конец которых лежит в Q_1, затем те, левый конец которых лежит во Q_2 и так далее до Q_p. Если же левые концы запросов лежат в одной части, то раньше идет тот запрос, у которого правый конец меньше.

Проследим за шагами левых и правых концов при таком подходе. Левые концы в одной группе Q_i совершают <= n / p шагов, при переходе к Q_{i+1} <= 2 * n / p шагов. Поэтому всего левые концы совершают <= n / p * n + 2*n шагов (второе слагаемое порядка O(n), оно неинтересно).
Правые концы в одной группе движутся только вправо, это дает <= n * p шагов в ходе всего процесса, при переходе же к следующему Q_i грубо оценим переход n шагами, поэтому в итоге имеем <= 2 * n * p шагов.
Итого количество шагов <= n/p * n + 2 * n * p. Выбирая p, равное sqrt(n), получаем нужную асимптотику.

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

Замечание 1. Никакой особенности, кроме неаддитивности, у функций из условия не было.

Замечание 2. В случае n, не равного t, получается оценка 2 * n * sqrt(t), и она точна (если внимательно проследить за доказательством, легко построить подпирающий оценку тест).



E. Длинная последовательность.

Для начала заметим, что есть только 49 вариантов входных данных.
Поэтому решения, которое предподсчитает все ответы за конечное время (даже если оно будет превышать лимит) будет вполне достаточно.

Для начала решим чуть более частную задачу. Предположим, что c1, c2, ..., ck и a0, a1, ..., ak - 1 нам уже даны и мы хотим проверить является ли последовательность {ai} построенная по ним длинной. Если она периодична, то среди k-кортежей as, as + 1, ..., as + k - 1 встречаются все ненулевые k-кортежи. Поэтому нет разницы с какого из них начинать последовательность. Поэтому свойство последовательности <<быть длинной>> зависит только от ci, и a0, a1, ..., ak - 1 мы можем выбрать любыми удобными. Будем теперь считать, что a0, a1, ..., ak - 2 --- нули и ak - 1 = 1.

Рассмотрим матрицу перехода A последовательности: при i < k ее i-ая строка равна (0, 0, ... 0, 1, 0, ..., 0), где 1 стоит на (i + 1)-ой позиции, а последняя строка равна (ck, ck - 1, ..., c2, c1).

Обозначим xs вектор-столбец (as, as + 1, ..., as + k - 1)T.
Тогда для любого целого s: A· xs = xs + 1.
Последнее равенство это просто переформулировка рекуррентного соотношения:

an = c1 * an - 1 + c2 * an - 2 + ... + ck * an - k.


Тогда, применяя A последовательно получаем: Ap· xs = xs + p для любого p ≥ 0. Таким образом p --- период тогда и только тогда, когда для всякого вектора x, появляющегося в нашей последовательности: Ap· x = x. Если последовательность не длинная, то априори не очевидно, что Ap единичная матрица, поскольку x принимает не все возможные значения. 

Но мы выбрали x0 = (0, 0, ..., 0, 1), поэтому ясно, что вектора x0, x1, x2, ..., xk - 1 образуют базис. Отсюда если Ap· xi = xi для всякого i, то Ap --- единичная матрица.

Таким образом, нам достаточно сделать следующее:

1) Построить матрицу A по коэффициентам ci.

2) Проверить, что A2k - 1 --- единичная матрица. (Если нет, то 2k - 1 не период.)
Если 2k - 1 период, то нам осталось проверить, что он минимальный. Любой период делится на минимальный, поэтому нам надо проверить лишь делители 2k - 1.

3) Построить все максимальные делители 2k - 1 и проверить, что они не периоды (снова с помощью A).

(Под максимальным делителем n подразумевается делитель вида n/q, где q --- простое).

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

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

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

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

Добрый день, участники и зрители!

Напоминаю вам, что подходит к концу отборочная стадия открытого турнира Яндекса по программированию "Яндекс.Алгоритм".
Вместе с тем это означает, что пришло время самого главного её события: отбора на финальный раунд, который пройдет в июле, на базе Летней школы Яндекса.
Это значит, что сегодня двести лучших участников турнира (по результатам предыдущих отборочных раундов) сразятся за право попасть в Топ-15 мирового олимпиадного сообщества.

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

Обращаю внимание, что сегодня стоимости задач будут следующие - 500, 1000, 2000, 2500, 2500.
В связи с этим у меня персональная просьба к участникам: не забудьте прочитать условия всех задач,
ведь упорядочивание по сложности - вещь сугубо субъективная.

Раунд будет рейтинговым для всех участников.

Желаю удачи участникам, всем же остальным - зрелищного раунда!


Раунд завершен. Согласно результатам, 19 участников решило хотя бы три задачи, один (победитель) решил четыре. Первые три места заняли Петр Митричев, Геннадий Короткевич и Сергей Копелиович.

Кроме того, определились пятнадцать финалистов, и это: Petr, tourist, Burunduk1, ivan.metelsky, dzhulgakov, e-maxx, LayCurse, rng_58, pieguy, zeliboba, ktuan, levlam, wata, dolphinigle, Progger .

Задачи оказались действительно непростыми. Ссылка на полный разбор.

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

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

Автор yaro, 14 лет назад, По-русски
Будем решать задачу для каждой точки P отдельно и решим ее за О(N). Видимо, самый простой способ - посчитать количество треугольников, не содержащих P, а затем вычесть это количество из общего количества треугольников.

Рассмотрим тройки вершин A, B, C, образующих треугольник, таких что P не лежит в ABC, кроме того, AB разделяет P и C и P лежит в многоугольнике справа от АB по часовой стрелке. Заметим, что для каждого треугольника его вершины в некотором порядке образуют такую тройку и каждая тройка дает нам треугольник. То есть можно вместо треугольников без P считать такие тройки.

Рассмотрим произвольную вершину многоугольника, мы хотим, чтобы она стала вершиной А в тройке. Тогда множество вершин, подходящих на место B в тройке мы получим идя диагоналями от A по часовой стрелке, пока не дойдем до P. Причем для каждой подходящей вершины B количество треугольников - это расстояние между A и B (в вершинах) по часовой стрелке минус один. То есть для фиксированной А мы получаем сумму арифметической прогрессии по подходящим B.

Единственное, что осталось понять - как найти последнюю B для А. Ну а эта задача просто решается двумя указателями сразу для всевозможных А.

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

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

Автор yaro, 14 лет назад, По-русски
В этой задаче нужно было научиться отвечать на запрос количества красивых чисел от 1 до R.

Ясно, что чтобы проверить, делится ли число на все свои цифры, достаточно знать остаток числа при делении на наименьшее общее кратное (НОК) всевозможных цифр (обозначим M), то есть M = 8 * 9 * 5 * 7 = 2520. Стандартная динамика подразумевает следующие параметры состояния: длина уже построенного числа, флаг "строго меньше", текущий остаток по модулю M и маску набора цифр, которые уже встретились.

Первое замечание: можно хранить не маску набора цифр, а, опять же, НОК тех цифр, которые уже вошли в число. Это уменьшит количество возможных значений последнего параметра c 256 до 4 * 3 * 2 * 2 = 48 (4 - кол-во возможных степеней двойки и так далее).

Далее, переходы к новым параметрам при добавлении цифры стоит делать с предподсчетом (от остатка к новому остатку и от НОКа цифр к новому НОКу). Мы хотели и постарались поставить такие ограничения в задаче, чтобы и это решение не проходило (а это было непросто, так как разница между Java и C++ по времени выполнения была разительна; в итоге, к сожалению, часть решений обошла TL без нижеизложенной оптимизации).

Идея, которую хотелось увидеть в динамическом решении — числовая. Если добавлять цифры с конца, то можно заметить, что на остаток числа при делении на 5 влияет лишь последняя цифра. Таким образом, можно хранить флаг "последняя цифра = 5 или 0" и запрещать переходы по цифре 5, если флаг не выставлен. Эта идея уменьшает количество состояний в 5 * 2 / 2 = 5 раз. Такое решение без проблем проходило на любом языке, но есть и более сильные оптимизации, тоже основанные на идее делимости (например, можно провернуть этот же трюк с двойкой).

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

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

Автор yaro, 14 лет назад, По-русски
Доброе утро (день, вечер, ночь)!

Авторы сегодняшнего раунда — Rei и yaro. Мы долго выбирали задачи для раунда и надеемся, что итоговый вариант вам понравится.

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

Удачи!

UPD. Разборы: А B C D E

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

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

Автор yaro, 14 лет назад, По-русски
А. Провинился — на кухню.
В задаче нужно было понять, какое максимальное x, упомянутое в условии, можно взять, чтобы каждого из ингредиентов хватило. Ясно, что это x = min(b_i / a_i). Осталось взять минимум из двух объемов — получившегося объема супа и объема кастрюли.

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

С. Взлом сейфа.
Ответ в этой задаче всегда положительный, то есть четыре единицы получить можно всегда. Действуя жадно (то есть делая операциями соседние числа четными и деля их на два), легко за логарифмическое (и получающееся, конечно, меньше 1000) количество операций добиться того, что сумма чисел будет не больше шести и дальше справиться со случаями, как многие и поступили. В чистом виде жадность не проходила, например, на тесте (1 1 1 2).
Есть и другие подходы, позволяющие избавиться от возни с "маленькими" случаями.

D. Странный город.
Расставим в вершины некоторые числа a_i. Тогда если ребру присвоить цену, равную сумме чисел на его концах, то сумма цен ребер вдоль любого гамильтонова цикла будет равна удвоенной сумме a_i. Осталось придумать такие числа a_i, чтобы их попарные суммы были различны (т.к. цены ребер должны быть различны). Причем такие варианты, как степени двойки или числа Фибоначчи, не годятся из ограничения 1000 на цену ребер. Тогда будем набирать a_i жадно, то есть новое a_i не должно быть равно (a_p + a_q - a_k) для любых уже выбранных чисел a_p, a_q, a_k. Такой выбор чисел в вершинах уже проходит. Причем, как несложно видеть, a_n = O(n^3), т.к. "блокирующих" троек (p, q, k) — O(n^3).
Другая идея заключается в том, что должно выполняться равенство AB+CD=AC+BD для любых различных вершин A,B,C,D (в сумме — стоимости соответствующих ребер), так как гамильтонов цикл с ребрами AB и CD "перестраивается" в цикл с ребрами AC и BD, а суммы у этих циклов должны быть равны.
Из этого, если хотите, можете понять, каким образом жюри точно и быстро проверяло ваши ответы.

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

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