Чему научитесь
- Переводить запись в число схемой Горнера и обратно делением
- Обозначать цифры больше девяти буквами и не терять ноль
- Пользоваться свойством суммы цифр по модулю b − 1
- Считать цифры чисел до 10^18 по разрядам, а не перебором
Как устроено занятие
Сначала разбор с примерами и короткими проверками понимания — они нужны, чтобы поймать непонятое сразу, а не через три темы.
Дальше 15 задач лестницей: разминка, основа, со звёздочкой. Занятие засчитывается, когда решено 10 — остальные не пропадают и учитываются отдельно.
После занятия — вторая часть, ещё 15 задач на те же приёмы в новых сюжетах.
Сколько это займёт
Примерно час-полтора вместе с задачами. Сроков нет: можно закрыть вкладку и вернуться когда удобно — прогресс сохранится.
Число и его запись
Мы привыкли, что число и то, как оно написано, — одно и то же. Это не так.
Запись 101 в двоичной системе означает пять, в троичной — десять, в десятичной — сто один. Число во всех трёх случаях разное, а строчка одна. И наоборот: пятёрка записывается как 101, 12 и 5 в разных системах — число одно, записи разные.
Число — это количество. Запись — это способ его назвать. Всё занятие про переход между ними.
Что означает запись
В позиционной системе с основанием запись означает
Цифры — это числа от 0 до . Когда основание больше десяти, цифр не хватает, и берут буквы: A — это 10, B — 11, и так до Z — 35. Поэтому основание в задачах обычно не превосходит 36.
Заметьте: это ровно то, что мы делали на занятии про разбор числа по цифрам, только там было всегда равно десяти. Сейчас оно станет параметром.
Два направления перевода
Из записи в число
Идём по записи слева направо и на каждом шаге умножаем накопленное на основание, прибавляя очередную цифру:
value = 0
for symbol in text:
value = value * b + DIGITS.index(symbol)
Это короче, чем считать степени: степени тут возникают сами собой. В Python есть и готовое: int("1A", 16) вернёт 26.
Из числа в запись
Обратный ход — деление с остатком. Остаток от деления на — это последняя цифра, а частное — «всё остальное число»:
out = []
while value > 0:
out.append(DIGITS[value % b])
value //= b
text = "".join(reversed(out))
Две ловушки, обе в этих шести строчках.
Первая: цифры получаются с конца, поэтому строку надо перевернуть. Забыть — значит получить зеркальный ответ, который на палиндромах выглядит правильным.
Вторая: при value == 0 цикл не сделает ни одного шага, и результатом будет пустая строка вместо "0". Ноль приходится обрабатывать отдельно — и это единственный случай, когда запись не строится общим правилом.
Из системы в систему
Отдельного алгоритма не нужно. Переводим запись в число, число — в новую запись. Десятичная система работает просто перевалочным пунктом, а в программе даже её нет: есть значение переменной.
Проверка: перевод в систему
Программа переводит число в систему делением с остатком, но не обрабатывает ноль отдельно.
Что она выведет для ?
Сумма цифр и её главное свойство
Сумма цифр считается тем же разбором на разряды. Но у неё есть свойство, которое превращает её из упражнения в инструмент.
Сумма цифр числа в системе даёт тот же остаток при делении на , что и само число.
Почему? Потому что , а значит и любая степень . Тогда
Каждый разряд «теряет» свою степень основания, и остаётся просто цифра.
Что из этого следует
В десятичной системе . Отсюда сразу два школьных признака:
- число делится на 9 тогда и только тогда, когда на 9 делится сумма его цифр;
- число делится на 3 тогда и только тогда, когда на 3 делится сумма цифр (потому что 3 делит 9).
Никакой магии, просто . В двоичной системе , и свойство вырождается: по модулю единицы всё равно нулю.
Цифровой корень
Если складывать цифры повторно, пока не останется одна, получится цифровой корень. Из свойства выше он считается формулой без всякого цикла:
root = 0 if n == 0 else 1 + (n - 1) % (b - 1)
Проверьте на примере: для в десятичной сумма цифр 36, потом 9. Формула: . Сходится.
Проверка: запись в двоичной
Сколько единиц в двоичной записи числа 100? Введите целое число.
Палиндромы
Палиндром — запись, которая читается одинаково в обе стороны. Важно, что это свойство записи, а не числа: пятёрка в двоичной системе 101 — палиндром, а в троичной 12 — нет.
Проверка простая: получить список цифр и сравнить его с перевёрнутым.
digits = digits_in_base(n, b)
if digits == digits[::-1]:
...
Когда палиндромов надо посчитать много
Перебирать все числа до годится, пока не больше миллиона. Для до нужен другой ход.
Палиндром однозначно задаётся своей первой половиной. Возьмём половину 123, отразим — получим 12321 для нечётной длины или 123321 для чётной. Значит палиндромов длины ровно столько, сколько бывает первых половин: .
Отсюда и подсчёт, и построение палиндрома по номеру: числа с меньшим количеством цифр считаются формулой, а на последней длине достаточно сравнить построенный палиндром с самим .
Типичные ошибки
Ноль превращается в пустую строку. Разобрано выше. Проверяйте каждое решение на .
Забытый разворот. Цифры выходят с конца. Ошибка коварна тем, что на палиндромах ответ совпадает.
Цифры больше девяти напечатаны как числа. При десятка должна стать буквой A. Если печатать str(10), получится 10 — две цифры вместо одной, и запись станет неоднозначной.
Обычное деление вместо целочисленного. value / b даёт вещественное число, и на больших значениях цифры «поплывут». Только //.
Ведущие нули. Появляются, если переворачивать запись. Как правило, их надо просто отбросить — но проверьте, что именно требует условие.
Регистр букв. Договоритесь с условием: заглавные или строчные. int(s, b) в Python принимает оба, а вот вывод надо приводить к тому, что просят.
Как проверять себя
- и ;
- и — крайние основания;
- — первое число, у которого появляется второй разряд;
- — последнее однозначное.
Практика: пятнадцать задач
Лестница прежняя: пять разминочных, семь основных, три со звёздочкой. Зачёт при десяти решённых.
Напишите себе две функции — «запись в число» и «число в запись» — и дальше пользуйтесь ими. Почти половина задач занятия решается их комбинацией.
И проверяйте на нуле. В этом занятии он ломает больше решений, чем все остальные случаи вместе.
Перевод, который врёт дважды
Задача: «даны от 0 до и от 2 до 36; выведите запись числа в системе с основанием , обозначая цифры больше девяти заглавными латинскими буквами».
Ученик написал:
n, b = map(int, input().split())
digits = ""
while n > 0:
digits += str(n % b)
n //= b
print(digits[::-1])
На входах 10 2, 255 8 и 1000 3 ответ верный.
Здесь две независимые ошибки. Найдите обе, приведите к каждой конкретный вход и предложите исправление.