Для целого положительного числа x определим R(x) к...
تم الإنشاء في: ٣ مارس ٢٠٢٥
تم الإنشاء في: ٣ مارس ٢٠٢٥
Для целого положительного числа x определим R(x) как число, равное перевёрнутому x в десятичной записи. Например, R(123) = 321, R(1) = 1, а R(100500) = 5001.
Для данного числа S вычислите количество целых x таких, что 1 ≤ x и x+R(x) ≤ S.
В качестве ответа выведите остаток от деления этого количества на 10^9 + 7.
Всё бы ничего, но вот ограничения на S (0 < S < 10^100 000).
Ниже изложена идея того, как можно решить задачу, даже когда (порог суммы ) имеет до десятичных цифр. Ключ состоит в аккуратном «DP по цифрам» (digit DP), где мы перебираем возможные «половинки» числа и одновременно следим, чтобы сумма не превосходила .
Нужно найти количество целых положительных , таких что
где — это «перевёрнутое» в десятичной записи (например, , ).
Ответ требуется вывести по модулю . Число задано в виде строки длиной до цифр, т.е. прямой перебор всех невозможен.
Запишем ровно в цифрах, допуская ведущие нули (где — это число цифр в ). Ясно, что если имеет не более цифр, то нельзя брать длиннее (иначе сумма могла бы «выйти» за цифр).
Пусть . Нумеруем цифры (и вообще числа) справа налево, от индекса до . Обозначим:
Рассмотрим десятичную запись длины (с ведущими нулями). Тогда «перевёрнутое» — это те же цифры, но в обратном порядке.
Если мы смотрим на сумму
то её -я цифра (считая справа налево) получается как
а перенос в следующий (более левый) разряд есть
При этом:
Но ключевой момент: чтобы в сумме получилось ровно цифр (а не ), в самом старшем разряде перенос (carry) в конце должен быть 0. Иначе сумма выйдет за -значный предел и гарантированно превысит , если у тоже цифр.
Разобьём сумму на «движение» по разрядам справа налево.
У числа из цифр «пар» (или «полупар») будет штук, плюс одна «серединная» цифра, если нечётно. То есть всего шагов, где мы «выбираем цифры » (или пару цифр), будет
(Это количество «разрядов», которые действительно формируются за счёт цифр .)
После того, как мы «потратили» все цифры (то есть прошли таких шагов), могут остаться «верхние» разряды суммы (если чётно, это пар; если нечётно, пар плюс один средний разряд). На оставшихся более левых разрядах в самой сумме уже «не участвуют никакие новые цифры », потому что все цифр уже учтены. Там в сумме может остаться лишь перенос (carry), который постепенно «перетекает» дальше влево.
Сравнение с .
Мы ведём состояние «флаг меньше» (условно less
), означающий, что уже на каком-то предыдущем (более младшем) разряде сумма была строже меньше соответствующего префикса . Если мы ещё «не отстали» (т.е. less = 0
), то очередная цифра суммы не должна превышать соответствующую цифру . Если меньше — то переходим в состояние less = 1
. Если ровно равна — состояние less
не меняется. Если пытались сделать больше — переход невозможен.
Перенос (carry) может быть только 0 или 1 (ведь сумма двух цифр , плюс перенос 1 даёт максимум 19, что даёт перенос не больше 1).
DP по парам
c
дало «цифру» (младший разряд) (если less=0
) или любую (если less=1
).После этого шага получаем «цифру суммы» и «новый перенос» . Сравниваем с (если less=0
). Определяем, переходим ли в less = 1
(если ) или остаёмся в less = 0
(если ), или переход невозможен (если ).
Так мы дойдём до . В этот момент мы исчерпали все «живые» цифры .
Обработка «остаточных» разрядов
Начиная с и далее до , в сумме новые цифры не появляются (мы уже «ушли» за середину — все пары/серединная цифра учтены). Значит, сумма-разряд равен просто переносу carry
(0 или 1), а на следующий разряд перенос обнулится.
carry
с (если ещё не перешли в less=1
). После чего carry
станет 0, и идём дальше, пока не дойдём до старших разрядов.less=0
) перенос carry
окажется больше, чем , ветка невозможна. Если меньше — сразу less=1
. Если равно — less=0
сохраняется. После этого шага (разряда) перенос всегда «гаснет» (становится 0), потому что мы добавляем только 0 к нему.Учёт условия .
Поскольку мы разрешили у ведущие нули, в нашу DP-переборку попадёт и «число» (все нули). Его сумма с разворотом тоже будет 0, что, конечно, , но нарушает условие . Поэтому из ответа придётся вычесть 1, если сумма нуля действительно не превосходит (а она не превосходит, если , но в условии , так что действительно нужно вычитать 1).
Сложность.
less=0
), нам приходится перебирать все возможные суммы двух цифр (или удвоенной одной цифры) от 0 до 18 и смотреть, какой получится . Но это можно зашить в заранее посчитанные массивы переходов (чтобы каждый шаг делался за , аккуратно суммируя количество пар, дающие нужную сумму). При less=1
мы вообще не сравниваем с и можем сложить «оптом» все варианты.Итого всё укладывается в примерно или с небольшой константой, что при до реализуемо на практике (особенно на языках вроде C++). В Python потребуется аккуратная оптимизация (использование списков/массивов, предвычисление переходов, и т.д.), но тоже сделать можно.
Считать как строку, сохранить её длину .
Записать цифры в массив в обратном порядке (чтобы был младшим разрядом).
Подготовить вспомогательные массивы:
pairCount[ s ]
= число пар с , у которых . Это для «обычных» пар разрядов.symCount[ s ]
= число цифр , таких что . Это для «серединной» цифры при нечётной длине.Основной DP №1 (до «середины»), храним dp[i][carry][less]
— сколько есть способов «дописать» пары (или одну цифру, если это серединный шаг), чтобы сформировать разряды суммы от 0-го до -го согласованно с ограничением .
dp[0][0][0] = 1
(никаких цифр не выбрали, перенос 0, ещё не отстали от ). Остальные по 0.less=0
, сравниваем c . Если — пропускаем, если — следующий less=1
, иначе less=0
.dp[i+1][ next_carry ][ next_less ]
количество способов выбрать (пару или одиночную цифру), которые дают сумму .dp[ half ][ c ][ l ]
.Переход ко второй фазе (DP №2) — «доливание» оставшихся разрядов, где новых цифр уже нет, а в сумме идёт только перенос.
carry
. Новый перенос после такого разряда всегда 0, потому что к carry
мы больше ничего не добавляем.
less=0
, сравниваем carry
с . Если carry>t_i\), путь невозможен. Если
carry<t_i), то в следующее состояние less=1
. Если carry=t_i
, то less=0
сохраняем.dp2[n][ final_c ][ final_less ]
. При (вышли за старшие разряды) перенос final_c
должен быть 0 (иначе получилось бы -значное число, не подходящее под ).dp2[n][0][0] + dp2[n][0][1]
— это число способов задать (включая нулевой ), чтобы .Вычитаем 1 (если , что в задаче всегда так) — это исключает «число» , состоящее из одних нулей, так как оно не удовлетворяет .
Результат берём по модулю .
На практике вся эта логика упирается примерно в итераций, где на каждой «парной» итерации мы делаем фиксированное число проверок (перебор сумм 0..18 и т.д.), а на «доливающих» шагах — вообще действий. При до это объём порядка нескольких миллионов элементарных операций — реализовать можно. В языках компилируемого типа (C++/Rust) обычно без проблем; в Python нужно будет чуть аккуратнее оптимизировать, но тоже выполнимо.
Возьмём . Тогда , цифры «справа налево» будут .
. Значит, в первой фазе у нас будет всего (одна итерация) — «берём пару цифр ».
Пусть . Рассмотрим все пары с суммой .
Например, (то есть ): это даёт , значит less=1
. А сколько пар имеют сумму 0? Ровно 1 (это ). Тогда . Итак, .
(то есть ): пар сумма=1 ровно 2 — это и . Тогда увеличится на 2.
— пропускаем, так что суммы не пройдут при less=0
.
Итого после первого шага:
Теперь вторая фаза — «обрабатываем верхние разряды» с до . Это всего один разряд .
Здесь «сумма-разряд» равен текущему carry
. А нового переноса уже не будет — после добавления нуля он обнуляется.
Смотрим, какие есть .
. Значит, сейчас carry=0
, less=0
.
digit = carry=0
. Сравниваем с . Получаем digit < t_1
, значит переходим в less=1
, перенос становится 0.. Значит, сейчас carry=0
, less=1
.
less=1
, мы не ограничены . digit=0
. Перенос будет 0. Остаётся less=1
.Итого получаем . Всё остальное — по нулям.
Теперь мы вышли за старшие разряды . По условию финальный перенос должен быть 0
. Тогда складываем . Первое равно 0, второе — 3. Итого 3.
Вычитаем 1 за нулевой . Получается 2.
Проверим вручную, какие (двухзначные, разрешая ведущие нули) дают , причём .
Получились ровно и . То есть 2 штуки — сходится с расчётом.
(Заметьте, если отдельно пройтись по всем , то мы найдём ещё вроде бы дают , , … Но надо помнить, что мы рассматриваем ровно как «двухзначное» с ведущими нулями. Например, — это «02»; тогда сумма . Значит, «02» не годится. А вот — это «01» и действительно «01 +10=11», ошибка? Посчитаем аккуратнее: «01» в прямом десятичном смысле — это . Его перевёрнутая запись «10» — это (десять). Сумма . Всё верно, значит для «1» это считается как (цифры ) + (цифры ) = 11. А вот «02» в перевёрнутом виде «20» сумма 22, что уже не проходит. Аналогично «03» + «30»=33 и т.д. Только «05» + «50»=55 … все мимо.)
Таким образом, всё согласуется.
Такое решение укладывается в асимптотику (с небольшой константой ~19 при обработке пар), и позволяет обработать даже гигантское (длиной до сто тысяч цифр).