Об одной изящной задаче

15 ИЮНЯ 2022

------------------------------------------------------------------------

Наш телеграм-канал: t.me/@mailsgun_online

Youtube: youtube.com/@mailsgun_online

------------------------------------------------------------------------

Обновить


timyrik20
1 час назад

Уровень сложности
Простой
Время на прочтение
4 мин

Количество просмотров 1.4K

Хабр, привет! В этой статье хочу поделиться с вами одной изящной задачей из нашего прошедшего квеста, которая, как мне кажется, заслуживает вашего внимания.

Имеется функция magic(), принимающая три целочисленных аргумента, в теле которой определены константы a, b, c, являющиеся натуральными числами. Требуется определить значения констант a, b и c за минимальное количество вызовов данной функции.

Три вызова функции и система уравнений

Первое, что может прийти в голову при виде такой задачи — это составление системы линейных уравнений:

  • вызывая функцию с аргументами x = 1, y = 1, z = 1, мы получим значение выражения a + b + c

  • вызывая функцию с аргументами x = 1, y = -1, z = 1, мы получим значение выражения a - b + c

  • вызывая функцию с аргументами x = 1, y = 1, z = -1, мы получим значение выражения a + b - c

Итого мы имеем систему трех линейных уравнений с тремя неизвестными a, b, c. Такая система имеет единственное решение, и его можно найти различными способами, например, методом подстановки.

Три вызова функции без системы уравнений

Поскольку по условию задачи аргументами функции magic() являются целые числа, то мы можем использовать значение 0. Это позволит нам избавиться от ненужных вычислений и сразу найти значения констант a, b и c:

  • вызывая функцию с аргументами x = 1, y = 0, z = 0, мы получим значение константы a

  • вызывая функцию с аргументами x = 0, y = 1, z = 0, мы получим значение константы b

  • вызывая функцию с аргументами x = 0, y = 0, z = 1, мы получим значение константы c

Получили достаточно красивое решение: компактное и с минимумом вычислений. Однако и такое решение использует три вызова функции magic().

Можно ли сократить количество вызовов функции? Возможно ли решить задачу всего за два вызова функции magic()? Оказывается, можно — при условии, что константы a, b и c являются натуральными числами.

Напомним, что натуральные числа — это целые положительные числа, то есть числа 1, 2, 3, 4 и т. д. Множество натуральных чисел неограниченно сверху.

Два вызова функции

Возьмем произвольное число 8956734. Такое число можно разложить по степеням десяти (base = 10) в виде:

8956734 = 8cdot 10^6 + 9cdot 10^5 + 5cdot 10^4 + 6cdot 10^3 + 7cdot 10^2 + 3cdot 10^1 + 4cdot 10^0

С таким же успехом мы можем разложить данное число по степеням ста (base = 100):

8956734 = 8cdot100^3 + 95cdot100^2 + 67cdot100^1 + 34cdot100^0

или даже по степеням тысячи (base = 1000):

8956734 = 8cdot1000^2 + 956cdot1000^1 + 734cdot1000^0

Почему такое разложение работает? В первом случае число 10 больше, чем любая из цифр от 0 до 9, поэтому в сумме справа нигде не происходит наложения разрядов. Во втором случае число 100 больше, чем любое двухзначное число от 10 до 99. В третьем случае число 1000 больше, чем любое трехзначное число от 100 до 999.

Прежде чем переходить к самому решению, давайте вспомним, как мы можем посчитать количество цифр в произвольном натуральном числе. Для этого нам потребуется использовать десятичный логарифм (можно использовать и конвертацию в строку с последующим вызовом функции len(), но через логарифм получается интереснее).

Приведенный ниже код:

from math import log10
for num in [1, 13, 456, 1673, 56789, 258964, 1234567]:
    digit_len = int(log10(num)) + 1
    print(digit_len, 10**digit_len)

выводит:

1 10
2 100
3 1000
4 10000
5 100000
6 1000000
7 10000000

А теперь, собственно, решение задачи. ?

Делаем первый вызов функции magic() с аргументами x = 1, y = 1, z = 1, получаем значение суммы a + b + c, которое запишем в переменную total. Такое число total заведомо больше, чем каждое из чисел a, b и c. С помощью десятичного логарифма находим минимальную степень десяти, превышающую число total, которую запишем в переменную power.

Делаем второй вызов функции magic() с аргументами x = power**2, y = power, z = 1. В результате такого вызова мы получим число, которое и будет содержать все три константы a, b, c слева направо. Далее с помощью операторов // (целочисленное деление) и % (нахождение остатка) находим значения констант a, b, c по отдельности:

from math import log10
total = magic(1, 1, 1)                       # первый вызов функции
power = 10**(int(log10(total)) + 1)
numbers = magic(power**2, power, 1)          # второй вызов функции
a, b, c = numbers // power**2, numbers % power**2 // power, numbers % power
print(a, b, c)

Рассмотрим пример. Пусть функция magic() имеет вид:

Получаем:

total = 23 + 46 + 19 = 88
power = 10**2 = 100
numbers = 23*100**2 + 46*100 + 19 = 234619
a = 234619 // 100**2 = 23
b = 234619 % 100**2 // 100 = 46
c = 234619 % 100 = 19

Еще один пример. Пусть константы a, b и c состоят из разного количества цифр и функция magic() имеет вид:

Получаем:

total = 19567 + 4 + 231 = 19802
power = 10**5 = 100000
numbers = 19567*100000**2 + 4*100000 + 231 = 195670000400231
a = 195670000400231 // 100000**2 = 19567
b = 195670000400231 % 100000**2 // 100000 = 4
c = 195670000400231 % 100000 = 231

Обратите внимание: в числе numbers могут быть «лишние» нули, но это нестрашно. В момент нахождения самих констант a, b и c незначащие ведущие нули пропадут.

Обобщения на большое количество неизвестных

Приведенное решение особенно красиво тем, что его можно масштабировать на любое количество неизвестных констант. В исходной задаче их всего три, но их может быть и больше, скажем, 5 или 10. Единственное, что надо будет поменять, так это код, извлекающий значения констант из итогового числа numbers: его легко обобщить и переписать в виде цикла while.

Рассмотрим пример функции magic() с пятью константами:

Приведенный ниже код:

from math import log10
total = magic(1, 1, 1, 1, 1)                             # первый вызов функции
power = 10**(int(log10(total)) + 1)
numbers = magic(power**4, power**3, power**2, power, 1)  # второй вызов функции
constants = []
while numbers != 0:
    constants.append(numbers % power)
    numbers //= power
a, b, c, d, e = reversed(constants)
print(a, b, c, d, e)

выводит значения всех пяти констант:

314 271828 668 12 7652

Напоследок

В разобранном решении задачи с двумя вызовами функции magic() использовались степени десятки (base = 10) в качестве основания системы счисления. Обязательно ли их использовать? На самом деле нет! Мы сделали это исключительно для того, чтобы нам было чуток нагляднее, ведь мы привыкли иметь дело с десятичной системой счисления. В качестве основания системы счисления мы можем использовать значение total, равное сумме констант a, b и c, ведь оно заведомо больше, чем каждое из чисел a, b и c.

total = magic(1, 1, 1)                       # первый вызов функции
numbers = magic(total**2, total, 1)          # второй вызов функции
a, b, c = numbers // total**2, numbers % total**2 // total, numbers % total
print(a, b, c)

Пусть функция magic() имеет вид:

Получаем:

total = 432 + 4 + 67 = 503
numbers = 432*503**2 + 4*503 + 67 = 109301967
a = 109301967 // 503**2 = 432
b = 109301967 % 503**2 // 503 = 4
c = 109301967 % 503 = 67

Напишите в комментариях, понравилась ли вам задача? Возможно, у вас есть другое решение, которое тоже использует всего два вызова функции. Поделитесь им! А может быть, вам удалось придумать решение задачи всего за один вызов? Делитесь им тоже!

Разбор остальных задач квеста будет опубликован чуть позже.

Присоединяйтесь к нашему телеграм-каналу, будет интересно и познавательно! ?

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Понравилась ли вам задача?
очень понравилась
13

понравилась, но не более
10

не понравилась
6

Проголосовали 29 пользователей.
Воздержались 4 пользователя.

Серия занятий «Тренировки по алгоритмам 5.0» от Яндекса

Дата 1 марта – 19 апреля
Время
Место

Онлайн

Московский туристический хакатон

Дата 23 марта – 7 апреля

Место

Москва • Онлайн

Геймтон «DatsEdenSpace» от DatsTeam

Дата
Время 17:00 – 20:00
Место

Онлайн

PG Bootcamp 2024

Дата
Время 09:30 – 21:00
Место

Минск • Онлайн

EvaConf 2024

Дата
Время 11:00 – 16:00
Место

Москва • Онлайн

Ну и напоследок напоминаю вам о нашем новом проекте vkserfing bot.