Минимизация числа с операциями
創建於:2025年1月18日
創建於:2025年1月18日
У Дианы есть число n. Она может выполнять операции двух видов с этим числом: поменять местами любые две цифры числа, например, из числа 31 можно получить число 13, а из числа 201 — число 102;
увеличить число на 1.
В операции первого типа запрещается ставить цифру 0
на первое место в числе (иными словами, ведущие нули запрещены). Например, из числа 30
нельзя получить число 3.
Диане стало интересно, какое наименьшее число она может получить из n
при помощи этих операций? Операции разрешается делать произвольное число раз в любом порядке.
Входные данные
Первая строка содержит натуральное число n
(1≤n≤109).
Выходные данные
Вывести одно число — минимальное число, которое можно получить из n.
Система оценки
Баллы за каждую подзадачу начисляются только в случае, если все тесты для этой и необходимых подзадач успешно пройдены.
Примеры
Входные данные
5
Выходные данные
5
Входные данные
28
Выходные данные
12
Реши задачу на python
Анализ задачи
Нам дано число (от до ). Разрешены две операции:
Перестановка цифр числа в любом порядке, но без ведущих нулей.
Например, из можно получить , из — , а вот из напрямую получить (то есть ) нельзя, так как это убирает «ведущий» (в итоге) ноль.
Увеличение числа на 1 (то есть ).
Нужно за произвольное (в том числе ноль) число таких операций достичь как можно меньшего итогового числа.
Из условия (и особенно из примера ) видно, что иногда выгодно «наращивать» число, пока в его цифрах не появятся более маленькие цифры (например, «1» или «0»), а затем делать перестановку, чтобы эта маленькая цифра оказалась в начале.
Ключевые моменты:
Прямым «перебором вперёд» от до (где — число цифр в ) мы бы могли столкнуться с миллионами/сотнями миллионов шагов, что слишком велико для большинства ограничений по времени. Но на практике оказывается, что довольно быстро либо находим существенно меньшее число, либо «упираемся» в пределы.
Идея простого решения через эмуляцию с запоминанием уже посещённых чисел:
Пусть текущее число называется . Начинаем с .
Определим функцию , которая переставляет цифры в неубывающем порядке (при этом ведущий ноль исключаем — фактически ставим первую ненулевую цифру впереди).
Храним глобально переменную , в которую будем записывать минимальное число, которое видели в процессе.
На каждом шаге:
visited
), выходим из цикла (значит, началось повторение, дальше можем циклично бегать, не улучшая результат).visited
.Итоговый будет искомым ответом.
Такое решение зачастую довольно быстро находит «улучшения» (меньшие числа) и при этом останавливается, когда дальнейший рост уже не может помочь. Дополнительно за счёт множества посещённых состояний мы защищаемся от зацикливаний на одном наборе цифр.
Ниже приведён код такого решения на Python.
pythondef solve(): import sys sys.setrecursionlimit(10**7) n = int(sys.stdin.readline().strip()) # Функция, переставляющая цифры числа в наименьший возможный вариант # без ведущего нуля. def smallest_arrangement(x: int) -> int: digits = list(str(x)) digits.sort() # сортируем по возрастанию # Находим первую ненулевую цифру i = 0 while i < len(digits) and digits[i] == '0': i += 1 # Если все цифры оказались нулями (бывает если x=0, но тут x>=1 всегда) if i == len(digits): return 0 # на самом деле в задаче x никогда не 0 # Собираем: сначала первая ненулевая, потом все, что слева от неё - нули, потом оставшиеся return int("".join(digits[i:] + digits[:i])) visited = set() best_val = n cur = n # Определим границу, где число переходит из d-значного в (d+1)-значное # Это 10^len(str(n)), хотя на практике можно брать 10**(число_цифр cur). limit = 10 ** len(str(n)) # d+1-значное уже while True: if cur in visited: # Начинается цикл, дальше смысла искать нет break visited.add(cur) rearranged = smallest_arrangement(cur) if rearranged < best_val: best_val = rearranged if rearranged < cur: # Есть возможность переставить и сразу получить меньшее cur = rearranged else: # Иначе пробуем увеличить if cur + 1 >= limit: # Выход за предел в (d+1) цифру уже точно не даст меньшего break cur += 1 print(best_val)
Пояснения к коду:
smallest_arrangement(x)
int
.cur
) — если да, выходим.cur
. Получаем rearranged
. Это потенциально новый кандидат на минимум.rearranged < cur
, значит мы можем за одну операцию (перестановка) перейти к rearranged
, и это улучшит число — переходим.rearranged >= cur
) ничего не улучшается перестановкой, тогда делаем операцию «прибавить 1».В итоге в переменной best_val
будет самое маленькое число, которое можно достичь из описанными операциями.
Вход: 5
Вход: 28
21
и затем переставить в 12
, и это будет минимум.Таким образом, решение соответствует условию и примерам. Конечно, при самом большом (около ) оно всё ещё может делать заметное число итераций, но на практике обычно быстро «останавливается» (или находит цикл, или упирается в переход в -значность).
Данный подход — один из самых прямолинейных и при аккуратной реализации в большинстве случаев проходит за разумное время.
n = int(input())
s = str(n)
L = len(s)
if L ==1:
print(n)
lower = 10**(L-1)
for m in range(lower, 10**L):
if m >n:
break
m_str = str(m)
max_perm = int(''.join(sorted(m_str, reverse=True)))
if m < max_perm <=n:
print(m)
print(n)
Доработай данный код по условию задачи
Ниже приведён один из способов доработки кода так, чтобы программа действительно находила минимальное число, достижимое из с помощью описанных операций:
Основная идея решения:
Код на Python:
pythondef solve(): import sys sys.setrecursionlimit(10**7) n = int(sys.stdin.readline().strip()) # Функция, переставляющая цифры x в минимально возможный порядок без ведущих нулей. def smallest_arrangement(x: int) -> int: digits = list(str(x)) digits.sort() # сортируем по возрастанию # Ищем первую ненулевую цифру, чтобы поставить её в начало i = 0 while i < len(digits) and digits[i] == '0': i += 1 # Если все нули (в задаче x>=1, так что теоретически недостижимо), но на всякий случай: if i == len(digits): return 0 # Собираем: первая ненулевая цифра + все нули, которые были перед ней, + остальные цифры return int("".join(digits[i:] + digits[:i])) # Если однозначное число, то никакой перестановкой сделать меньше не получится # (увеличение только сделает число больше). # Следовательно, ответ - само число: if 0 < n < 10: print(n) return visited = set() best_val = n cur = n # Верхняя граница, за которую мы не уходим (например, для n=28 берём limit=100). limit = 10 ** len(str(n)) # это 10^d, где d - число цифр в n while True: # Если уже встречали такое число, значит мы зациклились if cur in visited: break visited.add(cur) # Перестановка цифр cur в минимальный вид rearranged = smallest_arrangement(cur) # Обновляем глобальный минимум if rearranged < best_val: best_val = rearranged # Если перестановка улучшает число, "прыгаем" к нему if rearranged < cur: cur = rearranged else: # Иначе пробуем увеличить на 1 if cur + 1 >= limit: # Станем (d+1)-значным, что точно не даст меньшего break cur += 1 print(best_val) # ----------------------- # Так будет выглядеть запуск: # solve()
Как это работает:
smallest_arrangement(x):
В основном цикле:
visited
. Если встречаем число повторно, выходим (начнётся зацикливание).rearranged
. Если rearranged < cur
, переходим к нему (операция перестановки уменьшает число).Таким образом, мы постепенно перебираем комбинации «прибавить 1 и переставить», пока не найдём все варианты, которые могут улучшить результат. При этом алгоритм в разумный срок завершится, обновив \textit{best_val} до искомого минимума.