Подтема 7 из 7 · дополнительный урок
Длина и память программы
Ровно L команд, запрет повторять команду, движение в обе стороны: что запоминать, кроме числа.
Ограничение на программу
Дополнительный урок — для продвинутых: такие условия редки.
Сколько программ переводят 2 в 12 командами «+1» и «×2» ровно за пять команд?
Если условие ограничивает программу целиком — длину, последнюю команду, число умножений, — одного текущего числа мало: в состояние добавляют то, что нужно помнить о прошлом. Из 6 в 12 за одну команду попасть можно, а за две — нельзя: «6, и осталась одна команда» и «6, и осталось две» — разные вопросы.
Заданная длина
При заданной длине считают по паре «число, сколько команд осталось»; успех — когда команды кончились ровно в цели. Каждая команда уменьшает остаток на 1.
| Программа | Числа |
|---|---|
| 11112 | 2 → 3 → 4 → 5 → 6 → 12 |
| 21211 | 2 → 4 → 5 → 10 → 11 → 12 |
Других пятикомандных программ нет: короткие, например 121 (2 → 3 → 6 → 12), условию не подходят.
Третий аргумент left — сколько команд осталось. запоминает ответы для пар, чтобы не считать их повторно:
from functools import cache @cachedef f(x, y, left): if left == 0: return 1 if x == y else 0 if x >= y: return 0 return f(x + 1, y, left - 1) + f(x * 2, y, left - 1) print(f(2, 12, 5))1 if x == y else 0 — короткий выбор: 1, если x равен y, иначе 0.
Тонкости:цель раньше срока
Если цель достигнута, а команды ещё остались, ветка безуспешна: «+1» и «×2» только увеличивают число, и вернуться к цели нельзя. Поэтому x >= y даёт 0. Если команды могут уменьшать число, так отсекать нельзя.
Длина по участкам
А если из 2 в 12 нужно пройти через 6 ровно за пять команд?
При общей длине участки зависят друг от друга: перебирают, сколько команд уходит на первый участок, и складывают произведения. Обычное произведение 3 · 2 = 6 посчитало бы программы любой длины.
Из 2 в 6 можно за 2, 3 или 4 команды — по одной программе. Из 6 в 12 — только за одну команду. Сумма 5 команд выходит лишь при 4 + 1: ответ 1 · 1 = 1, программа 11112.
from functools import cache @cachedef f(x, y, left): if left == 0: return 1 if x == y else 0 if x >= y: return 0 return f(x + 1, y, left - 1) + f(x * 2, y, left - 1) print(sum(f(2, 6, k) * f(6, 12, 5 - k) for k in range(6)))Состояние
Что ещё, кроме числа, бывает нужно помнить?
Состояние — всё, что нужно знать о прошлом, чтобы считать будущее. Ограничение на одно число проверяют по самому числу; ограничение на программу целиком добавляет в состояние ещё одну величину.
| Условие | Состояние |
|---|---|
| обычное, запрет числа 9 | число |
| ровно L команд | число и сколько команд осталось |
| умножение ровно один раз | число и было ли умножение |
| нельзя повторять команду подряд | число и последняя команда |
Возвраты
Команды «+1» и «−1». Сколько программ переводят 2 в 4?
Если число может вернуться туда, где уже было, программ бесконечно много — пока условие не ограничит длину или числа. Перед 2 → 3 → 4 можно пройти круг 2 → 3 → 2 один раз, два, сколько угодно: все эти программы разные.
Поэтому в таких задачах всегда есть рамка: ровно L команд или числа в заданных пределах. Её и переносят в функцию — отсечение «x > y» здесь не годится, а @cache не спасает от бесконечной рекурсии: ответ запоминается, только когда вызов уже завершился.
Запрет повтора подряд
Команды «+1» и «×2». Сколько программ переводят 2 в 20, если нельзя умножать дважды подряд?
Запрет повтора подряд хранят в состоянии как последнюю команду. Из 8, полученного умножением, умножать нельзя, а из 8, полученного прибавлением, — можно.
from functools import cache @cachedef g(x, y, last): if x > y: return 0 if x == y: return 1 result = g(x + 1, y, 1) if last != 2: result += g(x * 2, y, 2) return result print(g(2, 20, 0))В начале последней команды нет — передаём 0. Без запрета программ было бы 30.
Сколько чисел
Из 1 командами «+1» и «×2» выполняют не больше четырёх команд. Какое наименьшее число получить нельзя?
Вопрос «сколько чисел» — не «сколько программ»: одинаковые числа считают один раз. Поэтому хранят не счётчик, а чисел, полученных за 0, 1, 2, 3 и 4 команды:
current = {1}reached = {1}for step in range(4): current = {y for x in current for y in (x + 1, x * 2)} reached |= currentprint(sorted(reached))print(min(n for n in range(1, 100) if n not in reached))Ответ 11. В подсчёте программ множество не годится: оно склеило бы разные программы с одинаковым результатом.
Движение в обе стороны
Команды «+3» и «−2», старт 1, цель 6, ровно пять команд, число на экране всегда положительное. Сколько программ?
Если команды двигают число в обе стороны, рамку задаёт условие — длина и пределы чисел, а не «число больше цели». Из 7 командой «−2» можно вернуться к 5, поэтому ветку выше цели не обрывают.
Нужно прибавить 5 за пять команд: если «+3» выполнено a раз, «−2» — b раз, то 3a − 2b = 5 и a + b = 5, откуда a = 3, b = 2. Расстановок двух «−2» среди пяти мест — 10, но половина уводит число в ноль или ниже: например, «−2» первой даёт −1. Подходят 5.
def f(x, left): if x <= 0: return 0 if left == 0: return 1 if x == 6 else 0 return f(x + 3, left - 1) + f(x - 2, left - 1) print(f(1, 5))Итого
- Ровно L командчисло и сколько команд осталось
- Длина по участкамсумма произведений по длине первого участка
- Не подрядчисло и последняя команда
- Сколько чиселмножество, а не счётчик
- Возвратынужна рамка: длина или пределы
- Обе стороныне обрывать ветку выше цели
Проверьте себя
Ответь сначала сам, потом открой ответ.
ОтветСкрыть ответ
ОтветСкрыть ответ
ОтветСкрыть ответ
ОтветСкрыть ответ
Практика
Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.
Ровно пять команд
Команды «+1» и «×2». Сколько программ переводят 2 в 12 ровно за пять команд?
Введите число.
Подсказка
Храни в состоянии, сколько команд осталось.
Решение
Подходят 11112 (2 → 3 → 4 → 5 → 6 → 12) и 21211 (2 → 4 → 5 → 10 → 11 → 12) — две программы.
def f(x, y, left): if left == 0: return 1 if x == y else 0 if x >= y: return 0 return f(x + 1, y, left - 1) + f(x * 2, y, left - 1) print(f(2, 12, 5))Ответ: 2
Ровно четыре команды
Команды «+1» и «×2». Сколько программ переводят 2 в 12 ровно за четыре команды?
Введите число.
Подсказка
Подсчитай, сколько команд остаётся после каждого шага.
Решение
Ровно за четыре команды подходит только 2112: 2 → 4 → 5 → 6 → 12.
def f(x, y, left): if left == 0: return 1 if x == y else 0 if x >= y: return 0 return f(x + 1, y, left - 1) + f(x * 2, y, left - 1) print(f(2, 12, 4))Ответ: 1
Пять команд через 6
Команды «+1» и «×2». Сколько программ переводят 2 в 12 ровно за пять команд, если траектория содержит 6? Реши распределением длины между участками.
Введите число.
Подсказка
Распредели пять команд между участками 2 → 6 и 6 → 12.
Решение
На участке 6 → 12 бывает 1 команда (×2) или 6 (не помещается). Значит, на 2 → 6 — ровно 4 команды: только 1111. Одна программа: 11112.
def f(x, y, left): if left == 0: return 1 if x == y else 0 if x >= y: return 0 return f(x + 1, y, left - 1) + f(x * 2, y, left - 1) print(sum(f(2, 6, k) * f(6, 12, 5 - k) for k in range(6)))Ответ: 1
Без двух умножений подряд
Команды «+1» и «×2». Сколько программ переводят 1 в 16, если нельзя выполнять умножение два раза подряд?
Введите число.
Подсказка
Помни последнюю выполненную команду.
Решение
Состояние — пара «число, последняя команда»: после умножения умножать нельзя. Без ограничения программ 36, с ним — 23.
def f(x, y, last): if x > y: return 0 if x == y: return 1 total = f(x + 1, y, 1) if last != 2: total += f(x * 2, y, 2) return total print(f(1, 16, 0))Ответ: 23
Достижимые числа за пять команд
Команды «+1» и «×2», старт 2, не больше пяти команд. Какое наименьшее число больше 2 получить нельзя?
Введите число.
Подсказка
Собирай множество чисел, полученных за 1, 2, …, 5 команд.
Решение
За пять команд из 2 получаются 32 разных числа; все числа от 3 до 22 среди них есть, а 23 — нет.
now = {2}seen = {2}for step in range(5): now = {y for x in now for y in (x + 1, x * 2)} seen |= nowprint(min(n for n in range(3, 100) if n not in seen))Ответ: 23