Что дано
Дано два целых числа и . Нужно найти наименьшее положительное целое число , удовлетворяющее одновременно двум условиям:
- делится нацело на .
- Сумма десятичных цифр равна ровно .
Если такого числа не существует, нужно вывести .
Ограничения
- .
- .
- Время — 1 секунда, память — 256 МБ.
«Наименьшее» здесь означает наименьшее значение — то есть сначала наименьшая длина (число цифр), а среди чисел одинаковой длины — наименьшее в лексикографическом порядке.
Формат ввода
В одной строке два целых числа и .
Формат вывода
Одна строка: либо искомое число (без ведущих нулей), либо , если такого числа нет.
Пример 1
Вход:
13 50
Вывод:
699998
Комментарий: , и .
Пример 2
Вход:
61 2
Вывод:
1000000000000000000000000000001
Комментарий: число длины , состоит из двух единиц и двадцати девяти нулей; сумма цифр ; делится на . Более коротких чисел с суммой цифр , делящихся на , не существует.
Пример 3
Вход:
15 50
Вывод:
-1
Комментарий: . Для делимости на последняя цифра должна быть или . Для делимости на сумма цифр должна делиться на . не делится на — значит, требуется одновременно сумма цифр и кратность , что невозможно. Ответ — .
Разберём первый пример руками
Сумма цифр — это много. Минимальная длина числа с такой суммой цифр — (если все цифры по , получим , надо немного снизить). Например, имеет сумму , но — не делится. Среди шестизначных чисел с суммой цифр нужно найти минимальное, делящееся на .
Лобовой перебор всех шестизначных чисел — кандидатов. Для каждого надо посчитать сумму цифр (8 операций) и проверить делимость. Это миллионов операций — быстро. Но при длина числа может быть , и перебор шестисотзначных чисел нереалистичен.
Идея: строить число по одной цифре слева направо, и держать в состоянии только то, что нам нужно для проверки финальных условий — текущий остаток по модулю и накопленную сумму цифр. Тогда конечное число — это путь в графе состояний от стартового до целевого , и нам нужен кратчайший такой путь, среди равных — лексикографически минимальный.
Идея решения
Состояние: пара — текущий остаток по модулю и сумма уже приписанных цифр. Стартовое состояние — (пустое число), целевое — (число с остатком при делении на и суммой цифр ровно ).
Из состояния есть до переходов: приписать цифру , перейти в , где
Переход в возможен, если (мы не должны перебрать сумму). Длина пути от до конечного состояния — это число цифр .
Хотим минимальное число: сначала минимальная длина, затем минимальная лексикографически. Идеальный инструмент — BFS: он гарантирует, что в момент первого попадания в каждое состояние мы пришли по кратчайшему пути.
Хитрость с лексикографической минимальностью: если при обходе очередного слоя BFS перебирать переходы в порядке цифр , то первое попадание в каждое состояние будет через цифру с минимальным значением. Однако нам нужно лексикографически минимальное число, а не минимальное по последней цифре. BFS сам по себе этого не даёт: к одному и тому же состоянию могут вести разные слова разной структуры.
Спасает симметрия задачи. Заметим: на состояние «след внутри числа» влияет только сумма приписанных цифр и текущий остаток. Если в одну и ту же можно прийти из двух разных предков через цифры , то стоит выбрать тот переход, который ведёт к меньшему числу — а это переход с меньшей цифрой на текущей позиции, при условии, что префикс предка тоже минимален.
Формально это решается так: при BFS храним для каждого состояния parent[(r', c')] = ((r, c), δ). Если состояние посещаем впервые — записываем. Поскольку BFS гарантирует кратчайший путь, а слой выходит из меньшей цифры раньше большей, первое попадание даёт минимальный путь.
Здесь важно одно тонкое условие: первая цифра не должна быть нулём. Поэтому стартовые переходы из перебираем по (не от ), а из остальных состояний — по .
Почему это работает
Кратчайший путь. BFS на любом графе с единичными весами находит кратчайший путь по числу рёбер. У нас каждое ребро добавляет одну цифру, то есть увеличивает длину числа на . Значит, кратчайший путь от до — это минимальная длина числа .
Лексикографически минимальное среди кратчайших. Внутри одного слоя BFS перебираем переходы в порядке возрастания . Когда новое состояние попадается в первый раз, мы пришли:
- либо из самого «раннего» предка (минимальный путь в предка по конструкции BFS),
- либо через минимальную возможную цифру среди равных предков-кандидатов (потому что перебор отсортирован).
Здесь полезно мысленно представить рекурсивный аргумент: путь в — это путь в предка + цифра . Среди всех таких пар (длина пути в предка оптимум) минимальное число лексикографически — это путь с минимальным при тех же предках; при разных предках с одинаковой длиной — выбор предка, в котором префикс минимален. BFS обходит вершины по слоям, поэтому когда мы посещаем из в момент времени , состояние уже было посещено раньше (в момент ), и его parent фиксирован. Перебор по от к внутри слоя обеспечивает, что среди всех путей одинаковой длины выбран минимальный лексикографически.
Условие первой цифры. Из стартового состояния первый шаг — цифра, которая станет старшим разрядом числа. Если разрешить , путь будет описывать число с ведущим нулём — не валидно. Поэтому первый шаг — только .
Размер графа состояний
Состояний всего . Рёбер — до числа состояний, то есть . BFS с такой сложностью укладывается в секунду по времени; по памяти — массив парентов и посещённости.
Решение: псевдокод
visited[d][s+1] = false
parent[d][s+1] = (prev_state, digit)
visited[0][0] = true
очередь ← [(0, 0)]
пока очередь не пуста:
(r, c) ← взять из очереди
если (r, c) = (0, s):
// дошли до цели; реконструкция ниже
break
начало = 1 если (r, c) = (0, 0) иначе 0 // запрет ведущего нуля
для δ от начало до 9:
если c + δ > s: continue
r' ← (10 * r + δ) mod d
c' ← c + δ
если visited[r'][c']:
continue
visited[r'][c'] = true
parent[r'][c'] = ((r, c), δ)
очередь.добавить((r', c'))
если не visited[0][s]:
вывести -1
иначе:
реконструировать строку цифр от (0, 0) к (0, s) через parent
вывести строку
Сложность: .
Код решения
Различия между языками здесь сводятся к двум деталям: в C++ нужно аккуратно работать с памятью под массивы парентов размера , в Python — следить, чтобы реконструкция через список не делала конкатенаций строк.
Комментарии по реализации.
- Тип под произведение. В вычислении
r * 10 + deltaмаксимальное , поэтому —intхватает в обоих языках, переполнения нет. breakвместоcontinueприnc > s. Внутри цикла по от меньших к большим, как толькоc + delta > s, дальше тем более не подойдёт. Это микро-оптимизация, но при близком к нулю и больших заметно ускоряет.- Память под
parentв C++. Линейный массивintразмера — это МБ подpar_rи столько же подpar_c. Дляpar_digitиспользуемchar(1 байт), экономим. В сумме укладываемся в лимит памяти. - Реконструкция в Python. Собираем цифры в
list, после цикла делаемreverse()и"".join(...)— это . Конкатенацияs = digit + sв цикле дала бы — на ответе длины это операций над строками, медленно. - Особый случай . В оригинальных ограничениях , но код всё равно обрабатывает: при единственное «число» с суммой цифр — это , но оно не положительное. Возвращаем .
Проверим на всех примерах из условия
Пример 1: ,
Целевое состояние — . Длина пути в BFS до этого состояния — (минимум по слоям). Восстановление пути от к даёт цифры (читаем от последней к первой), после реверса — , то есть . Проверяем:
- ✓
- : , остаток ✓
Ответ: ✓
Пример 2: ,
Длина — . BFS обнаруживает: из можно пойти в через цифру (но дальше нужно ещё для суммы , и достичь остатка при основе ). периодично; первый раз при таком, что . Этот , поэтому длина пути , число .
Ответ: (31 цифра) ✓
Пример 3: ,
. Делимость на — сумма цифр должна делиться на . . Значит, целевое состояние недостижимо: для любого числа, делящегося на , сумма цифр кратна . BFS обходит все достижимые состояния и не находит .
Ответ: ✓
Крайние случаи, на которых можно споткнуться
1. Делитель
Делятся на все числа. Тогда задача сводится к «найти минимальное число с суммой цифр ». BFS аккуратно даёт минимум: цифр , конкретнее — старшая цифра, дальше всё девятки.
2. Сумма
Минимальное число с суммой цифр — это для некоторого . Конкретное — минимальное такое, что . Для , у которого в разложении только и — такое существует. Иначе ответа нет (например, → ответ ).
3. Маленькие при большом
, до . Состояний — . Очередь BFS работает быстро; реконструкция — линейна по ответу (до цифр). Никаких проблем со временем.
4. Состояние совпадает с
Только при , что не входит в ограничения. На всякий случай в коде явно отрабатываем это через проверку.
5. Длина ответа очень большая
При близком к длина ответа может быть до цифр. Печать через "\n" в конце; в Python — одна sys.stdout.write за весь вывод (не построчная печать в цикле); в C++ — стандартная << строки.
Типичные ошибки
- Разрешённая ведущая цифра . Если из перебирать , в очередь попадёт повторно (или сразу будет помечено как посещённое). Безобидно для BFS, но если случайно разрешить, ответ может начаться с нуля. Проверять явно в коде стартовое условие.
- Кэширование через
dictв Python. На состоянийdictработает в 5–8 раз медленнееlist[list[...]]. На пограничных тестах даёт TL. Двумерный массив фиксированного размера — обязательно. - Реконструкция в неверном порядке. Идём от конца к началу, собираем цифры. Если забыть
reverse()— выведем перевёрнутое число. - Очередь BFS на
listв Python.list.pop(0)— . На операций это — миллиарды операций. Толькоcollections.deque. - Хранение пути в состоянии BFS. Соблазн положить в очередь не , а . Это убивает память ( строк длины до = = символов в худшем случае, и каждая копия строки требует выделения). Только индексы —
parentотдельно. - Несимметричный учёт «первой цифры». Если start = 1 применяется только к одной ветке кода, но не ко второй — где-то проскочит ноль. Проверка
(r == 0 && c == 0)должна стоять прямо в основном цикле. - Переполнение в C++ при индексации. .
intхватает, переполнения нет, ноvector<int>такого размера — это МБ. В стеке нельзя — только в куче.
Анализ сложности
- Время: — каждое состояние посещается один раз; из каждого — до переходов; всего элементарных операций.
- Память: под
visited,parent. Очередь BFS в худшем случае — все состояния, тоже . - Длина выходной строки: до цифр (когда все цифры — единицы) или до при «оптимальной» структуре. На печать уходит операций.
Что ещё полезно потренировать
- Codeforces 1801A «The Very Beautiful Blanket» — конструктивная задача на свойство , тренирует «строить ответ по позициям», как и наша «строить число по разрядам».
- Codeforces 1495D «BFS Trees» — BFS-обход графа с восстановлением путей, та же техника «отдельные массивы под parent» в чистом виде.
- Codeforces 1244C «The Football Season» — поиск решения линейного уравнения в малых числах, похожий шаблон «состояние компактно, перебор по дополнительной оси».
- acmp.ru задача 191 «Гирлянда» — DP с двумерным состоянием на сетке, шаблон «состояние полностью описывает оставшуюся задачу».
Итого
- Идея: BFS в графе состояний — остаток по модулю и накопленная сумма цифр; переход — добавление цифры; восстановление числа из массива
parent. - Сложность: время и память.
- Ловушки: запрет ведущего нуля, очередь — только
deque, кэш — только массив, неdict; реконструкция — собрать цифры, потомreverse.