Значение арифметического выражения 3333 + 322 — 9111 — 9 записали в системе счисления с основанием 3.
Определите количество цифр 2 в записи этого числа.
В задании 14 ЕГЭ по информатике мы работаем с позиционными системами счисления. На первый взгляд тема выглядит необычно: огромные степени, непривычные основания, числа, которые невозможно целиком выписать на бумаге. Но внутри почти всех задач скрывается одна и та же спокойная идея: число состоит из разрядов, а каждый разряд можно по очереди получить с помощью деления с остатком.
Слово «позиционная» здесь главное. Значение цифры определяется не только самой цифрой, но и её местом. В десятичном числе 505 две пятёрки обозначают разные величины: левая — пять сотен, правая — пять единиц. Ноль между ними ничего не добавляет сам по себе, зато удерживает разряды на своих местах. Без него запись 505 превратилась бы в 55 и стала бы обозначать совсем другое число.
То, что сейчас кажется естественным, человечество осваивало долго. Например, вавилоняне пользовались позиционной системой с основанием 60, но долгое время обходились без полноценного обозначения пустого разряда: смысл приходилось восстанавливать по контексту. История нуля особенно хорошо показывает, насколько важна позиция цифры. Ноль понадобился сначала как «сторож места», а уже позднее стал восприниматься как самостоятельное число.
Для компьютера выбор основания тоже не является декоративной деталью. Внутри вычислительной техники особенно удобна двоичная система, а восьмеричная и шестнадцатеричная помогают компактно записывать длинные двоичные последовательности. В задании 14 основания могут быть и другими — например, 3, 25 или 27. Принцип от этого не меняется.
В этой линии можно выделить три распространённых типа заданий:
Каждому типу посвятим отдельную статью. Начнём с первого: научимся без ошибок переписывать выражение на Python и разбирать результат на цифры в любой системе счисления.
Прежде чем идти дальше, договоримся о языке. Само число — это количество, математический объект. А запись числа — способ показать его с помощью выбранного алфавита. Один и тот же объект можно записать по-разному: десятичное 10, двоичное 1010₂ и шестнадцатеричное a₁₆ обозначают одно и то же число. Меняется оболочка, но не величина.
Эта мысль избавляет от частой путаницы. Когда Python вычисляет выражение 3 ** 333 + 3 ** 22 - 9 ** 111 - 8, результат хранится как целое число. У него нет приклеенной надписи «я троичное» или «я десятичное». Система счисления появляется только в тот момент, когда мы решаем, как читать разряды этого числа. Именно поэтому в цикле нужно явно указать основание: остатки от деления на 3 покажут троичные цифры, а остатки от деления на 10 — десятичные.
Представьте одну и ту же мелодию, записанную обычными нотами и буквенными обозначениями аккордов. Мелодия не изменилась, изменился язык записи. Так же и с числом: основание определяет язык, на котором мы раскладываем значение по разрядам.
Из позиционного принципа следует формула записи. Если цифры числа в системе с основанием p равны aₙ, aₙ₋₁, …, a₁, a₀, то значение равно aₙ × pⁿ + aₙ₋₁ × pⁿ⁻¹ + … + a₁ × p + a₀. Последнее слагаемое a₀ не умножается на полную степень основания, поэтому при делении всего числа на p именно оно остаётся в остатке. Так математическая формула напрямую приводит нас к программному алгоритму.
Прежде чем писать программу, разберём саму математику. Любое неотрицательное целое число N при делении на основание p можно представить так:
N = q × p + r, где q — целая часть от деления, а r — остаток, причём 0 ≤ r < p.
Остаток r всегда находится в диапазоне от 0 до p − 1. А это как раз все возможные цифры системы счисления с основанием p. Поэтому r совпадает с последней цифрой записи числа. Частное q — всё число без этой последней цифры. Повторяя операцию, мы снимаем цифры справа налево, словно по одной бусине с нити.
Возьмём число 7233 в десятичной системе. Основание равно 10, поэтому делим на 10 и записываем остатки:
Остатки появились в обратном порядке: 3, 3, 2, 7. Если прочитать их с конца, снова получим 7233. Для подсчёта цифр переворачивать последовательность не нужно: нам важно лишь проверить каждый остаток.
Теперь сменим основание. Десятичное число 220 записывается в восьмеричной системе как 334. Проверим последнюю цифру: 220 = 27 × 8 + 4. Остаток 4 действительно совпал с последней цифрой восьмеричной записи. Затем 27 = 3 × 8 + 3 и 3 = 0 × 8 + 3. Получили остатки 4, 3, 3 — то есть запись 334 справа налево.
Первый тип задания удобно разделить на два больших шага. Сначала переносим арифметическое выражение в Python. Затем разбираем полученное число на цифры и считаем те, которые подходят условию.
Сложение и вычитание записываются привычными знаками + и −. Для умножения в Python используется одна звёздочка *, а для возведения в степень — две звёздочки **. Возьмём выражение 2 × 5² + 3 × 7⁴ − 20 и запишем его на языке Python:
num = 2 * 5 ** 2 + 3 * 7 ** 4 - 20
print(num)
Программа выведет 7233. Обратите внимание на порядок действий: степень выполняется раньше умножения, а умножение — раньше сложения и вычитания. Скобки ставим тогда, когда они есть в исходном выражении или когда длинную запись нужно безопасно перенести на несколько строк.
Пусть требуется определить количество троек в числе 7233. Создадим счётчик count. Пока число не стало нулём, будем получать последнюю цифру оператором %, сравнивать её с 3 и отбрасывать оператором //.
num = 2 * 5 ** 2 + 3 * 7 ** 4 - 20
count = 0
while num:
digit = num % 10
if digit == 3:
count += 1
num //= 10
print(count)
Ответ равен 2.
Условие while num читается как «пока число не равно нулю». В Python ноль считается ложным значением, поэтому более длинная запись while num != 0 дала бы тот же результат. Строка num //= 10 означает то же самое, что num = num // 10: мы делим число нацело и сохраняем частное обратно в num.
Если система счисления другая, меняется делитель. Для восьмеричной записи берём остаток от деления на 8 и делим нацело на 8; для 25-ричной — используем 25. В общем виде шаблон выглядит так:
num = выражение_из_условия
base = основание_системы
target_digit = нужная_цифра
count = 0
while num:
digit = num % base
if digit == target_digit:
count += 1
num //= base
print(count)
Этот шаблон рассчитан на положительное значение выражения — именно такие числа встречаются в рассматриваемых примерах. Отдельный крайний случай — само число 0: его запись состоит из одного нуля, хотя цикл while num не выполнится ни разу. Если в условии теоретически может получиться ноль, этот случай нужно проверить отдельно.
Иногда требуется считать не одну конкретную цифру, а целую группу. Тогда меняется только условие внутри цикла. Например, запись digit > 9 подсчитывает все цифры с числовым значением больше 9, а digit in (0, 2, 4) — только цифры 0, 2 и 4.
Перед написанием кода прочитайте условие как короткий рассказ. Сначала дано полностью определённое арифметическое выражение, без каких либо неизвестных. Затем говорится, в какой системе счисления записали значение. Наконец, задаётся вопрос о цифрах этой записи: сколько нулей, двоек или значений больше некоторой границы она содержит. Эта последовательность и выдаёт первый тип.
Подчеркните три элемента: само выражение, основание системы и условие на цифру. Можно даже выписать рядом небольшую тройку: «num — base — проверка». После этого почти весь код восстанавливается автоматически.
Не ориентируйтесь только на огромные степени. Большие числа встречаются и в других линиях, а здесь важна именно фраза о записи результата в некоторой системе счисления. И наоборот, отсутствие явной команды «переведите число» не должно смущать: подсчёт цифр уже означает, что запись нужно исследовать, пусть мы и делаем это остатками без построения строки.
Полезная привычка — сделать маленькую мысленную проверку на знакомом числе. Если основание равно 8, возьмите число 220 и убедитесь, что первый остаток равен 4. Если в коде внезапно получается другая цифра, значит, вы перепутали оператор, основание или переменную. Такая мини-проверка занимает несколько секунд, зато помогает понять ошибку, а не просто увидеть неверный ответ.
Проверьте и границы возможных цифр. В системе с основанием p ни одна цифра не может быть равна p или превосходить его. Поэтому num % p всегда должно давать значение от 0 до p − 1. Если условие просит считать цифры больше 9 в 27-ричной системе, это осмысленно: там существуют значения от 10 до 26. А в восьмеричной системе такое условие никогда бы не выполнилось.
Наконец, оцените знак выражения. Наш цикл рассчитан на неотрицательные числа. В заданиях этого типа выражения подобраны так, чтобы результат был положительным, однако при ошибке в знаке можно случайно получить отрицательное значение и столкнуться с неожиданным поведением целочисленного деления. Если программа не завершается или выдаёт странный результат, первым делом ещё раз сверьте все плюсы и минусы.
Начнём разбор первого типа с такого задания:
Значение арифметического выражения 3333 + 322 — 9111 — 9 записали в системе счисления с основанием 3.
Определите количество цифр 2 в записи этого числа.
Здесь основание равно 3, поэтому каждый остаток может быть только 0, 1 или 2. Ищем остатки, равные 2. Самая опасная часть — переписать степени без потери показателей: 9¹¹¹ превращается в 9 ** 111, а не в 9 * 111.
num = 3 ** 333 + 3 ** 22 - 9 ** 111 - 8
count = 0
while num:
if num % 3 == 2:
count += 1
num //= 3
print(count)
Программа выводит 131. Это и есть количество двоек в троичной записи результата.
Теперь рассмотрим задание с более высоким основанием и подсчётом значащих нулей:
Значение арифметического выражения 3 ⋅ 31258 + 2 ⋅ 6257 — 4 ⋅ 6256 + 3 ⋅ 1255 — 2 ⋅ 254 — 2025 записали в системе счисления с основанием 25.
Сколько значащих нулей содержится в этой записи?
Фраза «значащие нули» может насторожить, но здесь она лишь напоминает: считать нужно нули, которые входят в обычную запись числа. Нули слева можно дописать сколько угодно — 7, 07 и 0007 обозначают одно и то же число, поэтому такие ведущие нули в каноническую запись не входят. Нули внутри записи или в её конце уже занимают разряды и влияют на значение числа; именно их алгоритм и подсчитает.
Выражение длинное, поэтому переносим его на несколько строк и заключаем в круглые скобки. Так заметно проще сверить каждый знак с условием.
num = (3 * 3125 ** 8
+ 2 * 625 ** 7
- 4 * 625 ** 6
+ 3 * 125 ** 5
- 2 * 25 ** 4
- 2025)
count = 0
while num:
if num % 25 == 0:
count += 1
num //= 25
print(count)
Ответ программы — 10. Обратите внимание: мы не переводили огромное число в строку и не пытались придумывать символы для цифр 10–24. Для подсчёта нулей достаточно числовых остатков.
В третьем примере изменится условие на цифры: будем считать все значения, которые больше 9:
Значение арифметического выражения 2 · 21872020 + 7292021 – 2 · 2432022 + 812023 – 2 · 272024 – 6561 записали в системе счисления с основанием 27.
Определите в 27-ричной записи числа количество цифр с числовым значением, превышающим 9.
Теперь условию подходят сразу значения 10, 11, …, 26. В программе не нужно перечислять их по одному: очередная цифра равна num % 27, поэтому достаточно сравнить этот остаток с 9 (num % 27 > 9).
num = (2 * 2187 ** 2020
+ 729 ** 2021
- 2 * 243 ** 2022
+ 81 ** 2023
- 2 * 27 ** 2024
- 6561)
count = 0
while num:
if num % 27 > 9:
count += 1
num //= 27
print(count)
Получаем 3367. Хотя само число содержит тысячи цифр, программа рассматривает их по одной и не хранит отдельную строковую запись в 27-ричной системе.
Можно встретить и другой подход: написать собственную функцию перевода, собрать строку из цифр, а затем воспользоваться методом count. Такой способ тоже имеет право на жизнь, но для первого типа задания он часто делает решение длиннее. Нам не нужна вся запись одновременно — достаточно узнать каждую цифру и сразу проверить её.
Числовой алгоритм особенно удобен для оснований больше 10. Остаток 17 остаётся обычным числом 17, и нам не приходится решать, каким символом его показать. Если же строить строку, пришлось бы заранее договориться, что значения 10–35 обозначаются буквами. Получается, работа с остатками ближе к самой математике задачи и меньше зависит от оформления записи.
При этом важно понимать, а не заучивать цикл. Строка num % base отвечает на вопрос «какова последняя цифра?», а num //= base — «что останется, если эту цифру убрать?». Если можете проговорить две строки словами, шаблон легко восстановится даже тогда, когда вы забыли точную запись.
Мы разобрали первый тип задания 14 и увидели, что его основа — обычное деление с остатком. Остаток от деления на основание системы даёт последнюю цифру, а целая часть от деления оставляет число без этой цифры. Повторяя два действия в цикле, можно исследовать запись числа в любой системе счисления, не переводя её вручную.
Всегда обращайте внимание на основание системы и на условие проверки. В обеих строках — с оператором % и с оператором // — должно стоять одно и то же основание. Следите за степенями, знаками и скобками в длинном выражении; полезно переносить его на несколько строк и сверять по одному слагаемому. Если ищется группа цифр, изменяйте только условие внутри цикла, а не весь алгоритм.
В следующей статье перейдём ко второму типу задания 14. Там внутри чисел появится неизвестная цифра x: составим алфавит системы счисления, научимся перебирать его в нужном направлении и разберёмся, как функция int переводит запись числа в десятичное значение.