Мы уже разобрали четыре типа задания 15. Остался ещё один, встречающийся в сборниках: задачи с множеством всех натуральных делителей. Сначала поймём их математический смысл, а затем соберём программное решение.
Задания данного типа характерны наличием фразы «множество всех натуральных делителей». В остальном же здесь присутствуют привычные нам отрезки и логическое выражение. Вот только найти здесь надо не какое-то значение числа из выражения, не длину отрезка, а число, делители которого составляют некоторое непустое множество C, фигурирующее в логическом выражении.
Несмотря на необычную формулировку, перед нами снова задача о включении множеств. Только теперь точки множества C расположены не сплошным отрезком, а являются отдельными делителями числа y.
Поэтому сначала разберём математический смысл условия без программы, а уже затем научим Python строить нужное множество полностью и без пропусков.
Нам понадобятся обычные логические операторы, функция all и аккуратный поиск всех собственных делителей числа, то есть делителей без 1 и самого числа.
Собственным делителем здесь будем называть натуральный делитель, отличный от 1 и самого числа. Например, у 12 это числа 2, 3, 4 и 6.
У простого числа таких делителей нет. Поэтому условие отдельно говорит, что C непусто: простые значения y рассматривать нельзя.
Давайте же приступим к разбору алгоритма решения пятого типа 15 заданий!
Алгоритм решения
Сначала читаем импликацию как включение множеств
Формула (x ∈ C) → ((x ∈ A) ∧ ¬(x ∈ B)) должна быть истинна при любом x. Если x не входит в C, импликация истинна автоматически.
Значит, проверять нужно только элементы C. Каждый собственный делитель y обязан одновременно лежать в отрезке A и не принадлежать множеству B.
На языке множеств получаем короткую запись: C ⊆ A \ B. Читается она так: C является подмножеством разности A и B.
Именно эта фраза должна оставаться в голове во время решения: «все собственные делители y разрешены отрезком A и не запрещены множеством B».
Сразу отметим, что заданий данного типа немного, в данной статье будут представлены два примера из сборника Крылова и одно авторское задание. Начнём с такой формулировки из сборника:
На числовой прямой дан отрезок A = [3; 60]; B – множество всех натуральных делителей числа 177, отличных от единицы и от самого числа 177; C – множество всех натуральных делителей некоторого натурального числа y, отличных от единицы и от самого числа y (число y таково, что множество C непустое).
Укажите наибольшее возможное значение числа y, для которого выражение (x ∈ C) → ((x ∈ A) ∧ ¬ (x ∈ B)) истинно (т.е. принимает значение 1) при любом значении переменной x.
Импликация требует, чтобы каждый x из C одновременно принадлежал отрезку A и не входил в B. Поэтому удобнее сначала найти все собственные делители y, а затем проверить только их.
Разложим фиксированное число: 177 = 3 × 59. Поэтому B = {3, 59}. В отрезке [3; 60] разрешены все числа, кроме этих двух точек.
Итак, у искомого y каждый собственный делитель должен лежать от 3 до 60 и не равняться 3 или 59. Уже сейчас задача стала гораздо конкретнее.
Как найти все делители и ничего не потерять?
Делители возникают парами. Если d делит n, то вместе с ним делителем будет n / d. Например, для 36 пары выглядят так: 2 и 18, 3 и 12, 4 и 9, 6 и 6.
Достаточно перебирать d только до квадратного корня. После корня пары начнут повторяться в обратном порядке: проверка 12 снова приведёт к уже найденной паре 3 и 12.
Функция isqrt возвращает целую часть квадратного корня без погрешности вещественных чисел. Множество set автоматически убирает повтор у чисел-полных квадратов (это те, которые являются квадратом целого числа, как 36 здесь).
Давайте сначала напишем код функции, а затем его подробно разберём:
from math import isqrt
def proper_divisors(n):
divs = set()
for d in range(2, isqrt(n) + 1):
if n % d == 0:
divs.add(d)
divs.add(n // d)
divs.discard(n)
return divs
Проследим работу функции на числе 84. Целый корень из 84 равен 9, поэтому d пробегает значения от 2 до 9.
При d = 2 остаток равен нулю, и мы добавляем пару 2 и 42. При d = 3 — пару 3 и 28, при d = 4 — пару 4 и 21.
Числа 5 и 6 проверяются дальше: 5 не делит 84, а 6 делит и добавляет 6 и 14. При d = 7 появляется пара 7 и 12. Значения 8 и 9 не подходят.
В итоге функция вернёт {2, 3, 4, 6, 7, 12, 14, 21, 28, 42}. Делители 1 и 84 отсутствуют, как и требует условие.
Строка divs.discard(n) служит дополнительной страховкой. При текущем переборе от 2 само n в множество не попадёт, но явное удаление подчёркивает правило: само число собственным делителем не считается.
Почему перебор конечный? Если y составное, то у него есть пара собственных делителей d и y // d. Оба должны лежать в A, поэтому не превышают 60. Значит, y ≤ 60² = 3600.
Поясним оценку подробнее. У любого составного y есть делитель d не больше √y (записью «√Y» мы будем обозначать квадратный корень числа Y) и парный делитель y / d не меньше √y. Оба они собственные и должны попасть в C.
Если все элементы C не превышают правую границу 60, то и больший делитель пары не превышает 60. Следовательно, √y ≤ 60 и y ≤ 3600.
Эта граница включает все возможные ответы. Она не гарантирует, что каждое число до 3600 подходит, но гарантирует, что выше ответа уже быть не может.
Для каждого y получаем полное множество его собственных делителей:
from math import isqrt
def proper_divisors(n):
divs = set()
for d in range(2, isqrt(n) + 1):
if n % d == 0:
divs.add(d)
divs.add(n // d)
divs.discard(n)
return divs
for y in range(60 ** 2, 1, -1):
divs = proper_divisors(y)
Посмотрим, что произойдёт с квадратом простого числа. Для y = 53² = 2809 функция найдёт d = 53 и добавит 53 и 2809 / 53, то есть снова 53. В множестве останется одна точка {53}.
Число 53 лежит в [3; 60], не делит 177 и потому не входит в B. Значит, y = 2809 действительно подходит.
Множество C должно быть непустым. Каждый его элемент x должен лежать в [3; 60] и не быть собственным делителем 177:
from math import isqrt
def proper_divisors(n):
divs = set()
for d in range(2, isqrt(n) + 1):
if n % d == 0:
divs.add(d)
divs.add(n // d)
divs.discard(n)
return divs
for y in range(60 ** 2, 1, -1):
divs = proper_divisors(y)
if divs and all(3 <= x <= 60 and 177 % x != 0 for x in divs):
Разберём условие all по частям. Для каждого x из divs сначала проверяется 3 ≤ x ≤ 60, а затем — что 177 % x != 0.
Если хотя бы один делитель вышел из отрезка [3; 60] или оказался делителем фиксированного числа 177, результат all сразу станет False. Остальные элементы можно уже не проверять: кандидат y всё равно отброшен.
Если множество divs пусто, all формально вернул бы True. Именно поэтому перед ним стоит отдельная проверка divs: она исключает простые y и выполняет требование о непустом C.
Всё, как только условие станет истинным, то выводим текущее значение y и завершаем работу программы:
from math import isqrt
def proper_divisors(n):
divs = set()
for d in range(2, isqrt(n) + 1):
if n % d == 0:
divs.add(d)
divs.add(n // d)
divs.discard(n)
return divs
for y in range(60 ** 2, 1, -1):
divs = proper_divisors(y)
if divs and all(3 <= x <= 60 and 177 % x != 0 for x in divs):
print(y)
break
Давайте проверим работу нашей программы. Запускаем её и видим на экране число 2809. А это и есть ответ на данное задание!
Почему программа не выбирает, например, 59² = 3481? Его единственный собственный делитель 59 входит в запрещённое множество B, поэтому условие all ложно.
А 60² тоже не подходит: у него много собственных делителей, среди которых есть числа меньше 3 и числа, запрещённые делимостью 177. Перебор последовательно отбрасывает такие кандидаты.
Мы идём сверху вниз, поэтому 2809 — не просто подходящее значение, а первое подходящее и, следовательно, наибольшее.
Общая заготовка для A = [left; right] и множества B из делителей fixed_number (фиксированного числа. Здесь это было 177):
from math import isqrt
def proper_divisors(n):
divs = set()
for d in range(2, isqrt(n) + 1):
if n % d == 0:
divs.add(d)
divs.add(n // d)
divs.discard(n)
return divs
for y in range(right ** 2, 1, -1):
divs = proper_divisors(y)
if divs and all(
left <= x <= right and fixed_number % x != 0 for x in divs):
print(y)
break
Пример 1
Давайте по этому же алгоритму решим еще одно задание из сборника:
На числовой прямой дан отрезок A = [7; 26]; B – множество всех натуральных делителей числа 77, отличных от единицы и от самого числа 77; C – множество всех натуральных делителей некоторого натурального числа y, отличных от единицы и от самого числа y (число y таково, что множество C непустое).
Укажите наименьшее возможное значение числа y, для которого выражение (x ∈ C) → ((x ∈ A) ∧ ¬ (x ∈ B)) истинно (т.е. принимает значение 1) при любом значении переменной x.
Здесь ищем наименьшееy, поэтому перебор идёт по возрастанию. Верхняя граница равна 26²:
Снова сначала разбираем множества. Число 77 = 7 · 11, поэтому B = {7, 11}. Разрешены собственные делители из [7; 26], кроме 7 и 11.
Число 169 = 13² имеет единственный собственный делитель 13. Он лежит в отрезке A и не входит в B, поэтому 169 подходит.
Но почему оно наименьшее? На этот вопрос отвечает направление перебора: программа проверяет все y от 2 до 168 раньше него и отбрасывает каждое. Первое найденное значение автоматически минимально.
from math import isqrt
def proper_divisors(n):
divs = set()
for d in range(2, isqrt(n) + 1):
if n % d == 0:
divs.add(d)
divs.add(n // d)
divs.discard(n)
return divs
for y in range(2, 26 ** 2 + 1):
divs = proper_divisors(y)
if divs and all(7 <= x <= 26 and 77 % x != 0 for x in divs):
print(y)
break
Запускаем нашу программу и видим результат — число 169. Его и запишем в ответ.
Пример 2
И напоследок рассмотрим задание с авторской формулировкой:
На числовой прямой дан отрезок A = [2; 47]; B – множество всех натуральных делителей числа 69, отличных от единицы и от самого числа 69; C – множество всех натуральных делителей некоторого натурального числа y, отличных от единицы и от самого числа y (число y таково, что множество C непустое).
Укажите такое значение числа y, имеющее максимальное количество делителей, для которого выражение (x ∈ C) → ((x ∈ A) ∧ ¬ (x ∈ B)) истинно (т.е. принимает значение 1) при любом значении переменной x.
Обратите внимание, что теперь есть дополнительное условие для y. Мы ищем не просто наибольшее или наименьшее значение этого числа, а то, которое имеет максимальное количество делителей! Давайте посмотрим, как это повлияет на наш код.
Здесь нельзя остановиться на первом подходящем y. Нам нужно сравнить все подходящие числа по другой характеристике — количеству делителей.
Код считает собственные делители. Если считать все натуральные делители, к каждому набору добавились бы 1 и само y. Это одинаковые два элемента для каждого кандидата, поэтому победитель не изменится.
Функция proper_divisors остаётся той же. Меняются только отрезок A, число 69 и способ выбора ответа.
from math import isqrt
def proper_divisors(n):
divs = set()
for d in range(2, isqrt(n) + 1):
if n % d == 0:
divs.add(d)
divs.add(n // d)
divs.discard(n)
return divs
В res будем сохранять пары: количество собственных делителей и само y.
res = []
Перебираем все y до 47² и проверяем их полные множества делителей:
for y in range(2, 47 ** 2 + 1):
divs = proper_divisors(y)
if divs and all(2 <= x <= 47 and 69 % x != 0 for x in divs):
Для каждого подходящего y сохраняем len(divs) и само число:
res.append((len(divs), y))
Функция max сначала сравнит первые элементы кортежей — количества делителей. Затем по индексу 1 получим y:
Например, пары (4, 72) и (6, 80) сравниваются сначала по первому числу. Шесть больше четырёх, поэтому выигрывает y = 80.
Если количества совпадут, обычный max выберет пару с большим y. Если условию задачи нужен меньший y при равенстве, правило сравнения следует записать отдельно.
from math import isqrt
def proper_divisors(n):
divs = set()
for d in range(2, isqrt(n) + 1):
if n % d == 0:
divs.add(d)
divs.add(n // d)
divs.discard(n)
return divs
res = []
for y in range(2, 47 ** 2 + 1):
divs = proper_divisors(y)
if divs and all(2 <= x <= 47 and 69 % x != 0 for x in divs):
res.append((len(divs), y))
print(max(res)[1])
После запуска программы мы увидим на экране число 80. Оно и будет ответом на это задание.
Итоги
В пятом типе задания 15 мы прочитали импликацию как включение множеств: все собственные делители y должны лежать в разрешённой части отрезка A и не попадать в B. После такого перевода длинная формулировка превращается в понятную проверку каждого элемента C.
Как собрать решение с нуля:
Первый шаг — выписать собственные делители фиксированного числа и понять, какие точки запрещает B. Для 69 получаем 69 = 3 · 23, поэтому B = {3, 23}.
Второй шаг — переписать импликацию как требование к каждому делителю y. Если формула отличается от привычной, нельзя механически оставлять прежнее условие внутри all: сначала переведите её словами.
Третий шаг — определить направление поиска. Для наибольшего y идём от верхней границы вниз и останавливаемся на первом ответе; для наименьшего — снизу вверх.
Четвёртый шаг — обосновать верхнюю границу через пары делителей. Если правый конец A равен r и каждый собственный делитель обязан лежать в A, достаточно проверить y до r².
Пятый шаг — после получения ответа вручную построить C для найденного y. Такая проверка короткая и показывает, что программа решала именно заданную задачу, а не похожую.
Шаблон решения
from math import isqrt
def proper_divisors(number):
divisors = set()
for d in range(2, isqrt(number) + 1):
if number % d == 0:
divisors |= {d, number // d}
divisors.discard(number)
return divisors
for y in *1:
divisors = proper_divisors(y)
if divisors and all(*2 for x in divisors):
print(y)
break
В шаблоне *1 заменяется полным range. Например, для максимума при правой границе 60 пишем range(60 ** 2, 1, -1), а для минимума — range(2, 60 ** 2 + 1).
Вместо *2 записываем проверку одного делителя x. Для формулы из наших примеров это left <= x <= right and fixed_number % x != 0.
Не меняйте устройство шаблона без причины: сначала получаем divisors, затем проверяем непустоту, потом применяем all. Такой порядок соответствует математическому рассуждению и легче проверяется глазами.
Здесь *1 — обоснованный диапазон и направление перебора y, *2 — условие для делителя x. Если нужен экстремум (минимум или максимум) по дополнительной характеристике, сохраняйте пары в список и выбирайте min или max.
Всегда обращайте внимание на слово «непустое»: простые y необходимо исключать. Помните, что делители идут парами, поэтому перебор до √y должен добавлять оба числа. Границу y обосновывайте правым концом отрезка, а направление перебора — словом «наибольшее» или «наименьшее».
Если требуется максимум по количеству делителей, не завершайте цикл на первом кандидате. Сохраните все подходящие варианты вместе с их характеристикой и только затем выберите лучший.
На этом мы закончили разбор всех пяти типов задания 15 ЕГЭ по информатике. Теперь осталось превратить алгоритмы в навык — для этого переходите в наш тренажёр и попробуйте сначала объяснять каждую проверку словами, а уже затем писать код.
Разберём эту тему на бесплатном пробном занятии
Преподаватель покажет, как решать такие задачи, и подскажет, с чего начать подготовку. Группы до 8 человек, очно в Калининграде и онлайн.
Мы используем файлы cookie. Необходимые обеспечивают работу сайта: корзину и вход в личный кабинет. С вашего согласия мы также используем сервис Яндекс Метрика, чтобы анализировать посещаемость и улучшать сайт.
Подробнее — в политике конфиденциальности.