Олимпиада по информатике 2025-2026 муниципальный этап Хомяк и двоичные тайны

Олимпиада по информатике 2025-2026 муниципальный этап Хомяк и двоичные тайны

Скачать

1. Что делает твой алгоритм (коротко)

Задача: посчитать количество чисел x таких, что
0 ≤ x ≤ R и popcount(x) ≡ 0 (mod K).

Твой код делает следующее:

  1. Читает n, K, R (где R — десятичная строка).

  2. Переводит R в двоичную строку bin_str.

  3. Пусть L = len(bin_str) – длина двоичной записи R.

  4. Частный случай K > L
    Тогда popcount(x) никогда не может быть K, 2K, ...,
    единственный вариант с popcount(x) = 0 — это x = 0.
    0 ≤ R всегда верно, поэтому ответ = 1.

  5. Если K небольшой (≤ 120) — запускается битовый DP по модулю K:

    • Состояния:

      • dp_tight[r] – сколько чисел с остатком r по popcount mod K,
        у которых префикс точно совпадает с префиксом R.

      • dp_loose[r] – сколько чисел с остатком r,
        у которых префикс уже строго меньше префикса R.

    • Идем по битам R слева направо, на каждом шаге:

      • если бит R равен 0, в состоянии tight можно поставить только 0;

      • если бит R равен 1, можно поставить 0 (становимся loose)
        или 1 (остаемся tight, увеличиваем popcount).

      • из loose можно ставить и 0, и 1 всегда.

    • В конце ответ = dp_tight[0] + dp_loose[0] (все числа, где popcount % K == 0).

    Сложность ~ O(L * K).

  6. Если K большой – используется комбинаторика:

    • Сначала предвычисляем факториалы и обратные факториалы до L
      для биномиальных коэффициентов C(n, r) по модулю.

    • Функция count_with_exact_ones(c, bin_str, ...) считает,
      сколько чисел x, 0 ≤ x ≤ R, имеют ровно c единиц в двоичной записи.
      Это делается классическим приёмом:

      • идём по битам R,

      • когда в R стоит 1, есть вариант «поставить 0 здесь, а дальше выбрать need единиц» → добавляем C(remaining, need),

      • и вариант «поставить 1 и уменьшить need».

    • Затем перебираем c = 0, K, 2K, ... пока c ≤ L
      и суммируем count_with_exact_ones(c, ...).

    Это эффективно, когда K достаточно большой, так как тогда возможных c немного (примерно L / K).


2. Почему в Python 3 — 90 баллов, а в PyPy — 100?

По условиям задачи:
в последней группе тестов n ≤ 30000, значит:

  • длина двоичной записи RL ≈ n * log10(2) ≈ 100 000 бит;

  • при небольших K (например, K = 2, 3, …, 120)
    мы попадаем в ветку solve_small_K, т.е. выполняем DP сложностью:

O(L⋅K)≈100000⋅120=12 000 000 переходовO(L \cdot K) \approx 100000 \cdot 120 = 12\,000\,000 \text{ переходов}

Для C++/Pascal это легко в 1 секунду,
но чистый CPython 3.10 с такими плотными циклами по спискам
часто не успевает в лимит 1 секунда →
на самой большой группе тестов получается TLE или слишком долгий запуск,
и система ставит «Частичное решение: 90 баллов».

У PyPy 3.10-64:

  • есть JIT-компиляция,

  • длинные однотипные циклы сильно ускоряются,

и тот же самый код успевает за отведённое время → 100 баллов.

То есть:

  • Алгоритм у тебя абсолютно корректный (это видно по 100% в PyPy);

  • Проблема именно в производительности CPython на последней группе (n = 30000).