Сначала переведём условие на человеческий язык: ищем числа $n = p \cdot q$, где $p \le q$ – оба простые, и в записи каждого ровно одно «16». Печатаем само n и его наименьший множитель p – именно он идёт во второй столбец. Оговорка «не обязательно различных» разрешает $p = q$: полный квадрат простого тоже подходит.
- Прямой путь – разложить каждое $n$. Функция
prime_factors из блока «Заготовки» ниже раскладывает число на простые множители, остаётся проверить, что их ровно два и оба «хорошие». До пятого ответа надо перебрать 42 830 чисел, и на каждом простом $n$ – а простые среди них составляют примерно каждое двадцатое – деление идёт до самого корня, около 33 тысяч шагов. В сумме секунд десять – двенадцать. Для экзамена это уже годится: ответ получен, дальше – ускорение ради удобства.
- Выход на первом делителе. Полное разложение не нужно: первый найденный делитель $n$ – это и есть наименьший множитель
p, и он заведомо простой (будь он составным, раньше нашёлся бы его собственный делитель). Проверили p и частное $q = n / p$ – и break, дальше делить незачем. Около пяти секунд: делений на простых $n$ по-прежнему много, зато на всех остальных цикл обрывается сразу.
- Делить только на подходящие
p. Меньший из двух множителей не больше $\sqrt{n}$, а корень из наших чисел – около 33 216. Значит, p – одно из простых чисел до этой границы, в записи которых ровно одно «16». Таких всего 179 (из 4203 простых до 40000) – выписываем их один раз заранее в список small и делим $n$ только на них. Как только p нашлось, проверяем q и выходим: если $n = p \cdot q$ из двух простых, других простых делителей до корня у него нет. А проверка p * p > n обрывает перебор списка, когда кандидаты кончились. Меньше секунды.
| Что сделали |
Время (примерно) |
| разложение каждого $n$ |
10–12 с |
| выход на первом делителе |
~5 с |
делим только на список small |
~0,7 с |
Пригодны все три ступени: на экзамене нормально всё, что досчитывается быстрее секунд тридцати. Если список не приходит в голову, вторая ступень – это пять строк:
for p in range(2, isqrt(n) + 1):
if n % p == 0: # первый делитель – наименьший, он простой
q = n // p
if good(p) and good(q) and is_prime(q):
print(n, p)
found += 1
break
В решении ниже – третья ступень: код не сложнее, одна строка со списком small, а считается в семь раз быстрее.
Граница 40000 взята с запасом – она больше корня из любого проверяемого числа, так что ни один кандидат в p не потеряется. Лишние элементы списка отсекает не она, а p * p > n: как только кандидат перевалил за корень из текущего $n$, дальше пробовать нечего.