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

Автор thematdev, 3 года назад, перевод, По-русски

Этот блог основан на задаче 1591D - Ещё одна задача на сортировку, надеюсь вам он понравится.

Базовые понятия

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

Четность перестановки $$$p$$$ это четность количества инверсий в ней. Инверсия это пара $$$(i, j)$$$ такая, что $$$i < j, p_i > p_j$$$.

Композиция перестановок определяется так $$$(f \circ g)_x = f_{g_x}$$$, мы сначала применяем $$$g$$$, затем $$$f$$$.

Цикл это перестановка, где некоторые индексы циклически сдвинуты, например $$$p = \begin{pmatrix}1 & 5 & 2 & 4 & 3\end{pmatrix}$$$ это цикл $$$\begin{pmatrix}2 & 3 & 5\end{pmatrix}$$$.

Теорема. Любая перестановка это композиция непересекающихся циклов.

Доказательство. Посмотрим на граф с вершинами от $$$1$$$ до $$$n$$$, и ребрами $$$i \to p_i$$$ для всех $$$i$$$. Поскольку $$$p$$$ перестановка, входящая и исходящая степень любой вершины равна 1, значит наш граф это набор циклов, что и требовалось.

Лемма. Применяя транспозицию(меняя два элемента местами) мы меняем четность перестановки.

Эта лемма очень важна, поэтому остается в качестве упражнения читателю

Факт. Четность $$$(f \circ g)$$$ равна сумме четностей $$$f$$$ и $$$g$$$.

Доказательство. Применение транспозиции меняет четность, поэтому если $$$f$$$ представляется в виде $$$k$$$ транспозиции, то четность $$$k$$$ это четность $$$f$$$. Пусть $$$f$$$ представляется $$$k$$$ транспозициями, а $$$g$$$ представляется $$$m$$$ транспозициями, тогда $$$f \circ g$$$ может быть представлена как $$$k + m$$$ транспозиций, но четность $$$f \circ g$$$ это четность $$$k + m$$$, что в свою очередь является суммой чётностей $$$f$$$ и $$$g$$$, ЧТД.

Подсчёт числа инверсий и четности перестановки

Существует очень много способов посчитать количество инверсий за $$$O(n \log n)$$$.

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

Теорема. Перестановка представляется в виде композиции 3-циклов тогда и только тогда, когда она чётная.

Доказательство. 3-цикл чётный, поэтому композиция 3-циклов тоже четная. Осталось только доказать, что для четных перестановок такое представление существует.

Попробуем построить его итеративно, начиная с тождественной перестановки, последовательно применяя 3-циклы. На $$$i$$$-ой итерации мы предполагаем, что первые $$$i - 1$$$ элементов уже стоят на месте, и мы пытаемся поставить $$$i$$$-ый. Если $$$i$$$-ый элемент не на месте, то пусть в текущей перестановке он находится на месте $$$j$$$. Тогда $$$j > i$$$, и давайте применим цикл $$$n \to j \to i$$$, а если $$$j = n$$$, то $$$n - 1 \to j \to i$$$. Таким образом мы можем поставить на место $$$i$$$-ый элемент, и не нарушить расположение предыдущих.

Так мы можем сделать $$$n - 2$$$ шагов. Далее возможны две ситуации

  1. Последние два элемента на месте, значит мы нашли искомое разбиение, и наша перестановка была чётной.

  2. Последние два элемента не на месте, то есть они поменяны местами. Значит текущая перестановка отличается от нашей одной транспозицией, но текущая перестановка чётная, значит наша нечётная.

Ниже приведена реализация

Реализация

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

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

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

Автор thematdev, история, 3 года назад, По-русски

Я заслал решение с трех разных компиляторов и получил три разных вердикта(OK, WA 1, ML 1) по задаче 1553E - Permutation Shift с последнего раунда

123344376

123363613

123363479

Потом я запустил valgrind (g++ 11.1.0) и обнаружил странные "still reachable" адреса памяти, связанные с ios_base::sync_with_stdio. Потом я обнаружил, что он был в функции solve, которую я вызываю для каждого набора входных данных, а не в main, после чего я перенес его в main и получил OK на всех компиляторах.

valgrind output on sample

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

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

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

Автор thematdev, история, 4 года назад, перевод, По-русски

Привет, Codeforces! У меня 13 успешных взломов по задаче D, а также мое решение не прошло систесты(возможно один из моих :) ) У нас есть две части решения

Первая часть посчитать для числа наименьший делитель, это делается решетом Эратосфена за $$$O(N \log N)$$$ если просеивать всеми числами, если только простыми, то за $$$O(N \log \log N)$$$, или же линейным решетом за $$$O(N)$$$.

Вторая часть это подсчёт ответа. Мы должны для каждого числа вида $$$\frac{\frac{x}{d_x} + d}{c}$$$ прибавить $$$2^k$$$, где $$$k$$$ -- количество различных простых делителей. ($$$d_x$$$ делится на $$$x$$$). Если не предподсчитывать, то это будет $$$O(\log x)$$$ на делитель, иначе $$$O(1)$$$.

Теперь попробуем подобрать числа $$$c, d, x$$$ чтобы взломать решение за $$$O(t\sqrt{x} \log x + N \log \log N)$$$. Поскольку мы считаем количество различных простых делителей у чисел вида $$$\frac{\frac{x}{d_x} + d}{c}$$$, мы будем использовать $$$c = 1$$$. Также мы факторизуем число $$$x$$$. Сначала найдем $$$x$$$ с наибольшим количеством делителей, и этот $$$x$$$ есть $$$8648640$$$. Затем попробуем максимизировать

$$$\sum\limits_{d_x \mid x} s(x/d_x + d)$$$

где $$$s(n)$$$ -- сумма степеней простых в факторизации $$$n$$$(именно столько операций мы делаем).

Теперь у нас есть мощный тест. 10000 раз по $$$c=1, d=x=8648640$$$. Ниже вы можете посмотреть взломанные таким способом решения https://codeforces.me/contest/1499/hacks/714784

https://codeforces.me/contest/1499/hacks/714699

https://codeforces.me/contest/1499/hacks/714697

https://codeforces.me/contest/1499/hacks/714692

https://codeforces.me/contest/1499/hacks/714688

https://codeforces.me/contest/1499/hacks/714629

https://codeforces.me/contest/1499/hacks/714683

Но некоторые решения не сломались, и тому много причин. Иногда люди предподсчитывали наименьшие делители сразу возведенные в степень вхождения, и тогда в среднем по Second Merten's Theorem ответ будет искаться за $$$O(\log \log x)$$$ для делителя в среднем и наше $$$d$$$ уже не работает. Но основная проблема заключается в работе с памятью и кешем процессора, поэтому нужно генерировать различные числа с большим количеством делителей. Это можно сделать рандомом. Но как использовать рандомизированный генератор на кфе? Достаточно просто задать сид для mt19937.

Пример такого взлома

User: haruki_K

Submission: 110353111

Существуют и другие сильные взломы, например $$$d = (3 \cdot 5 \cdot 7 \cdot 11 \dots) - x$$$. Но также есть более странные вещи. Например три посылки(все из них мои, и все из них имеют разное время на макстесте): 110370237, 110420633, 110439475

Если вы можете это объяснить, то пожалуйста, сделайте это.

Резюмируя, я хочу сказать, что ограничения были несбалансированы, потому что люди которые придумали идею, и просто не предподсчитали ответ, получили TL. Ограничения в ОБУЧАЮЩИХ раундах не должны быть такими жесткими, особенно учитывая такую погрешность (см.выше).

Также я хочу поблагодарить plagues за отправление моего решения, чтобы порофлить надо мной :)

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

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