Advertisement
pakuula

Поиск чисел с пятью нечетными делителями

May 12th, 2025
385
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.62 KB | None | 0 0
  1. """
  2. Найдите все натуральные числа, принадлежащие отрезку [35000000;40000000], у которых ровно пять различных нечётных делителей (количество чётных делителей может быть любым). В ответе перечислите найденные числа в порядке возрастания.
  3.  
  4. Для решения задачи воспользуемся тем, что число имеет ровно 5 различных нечётных делителей, только если оно представимо в виде 2^k * p^4, где k - степень двойки, p - простое число.
  5. """
  6. import math
  7. import time
  8. from typing import Any, Callable, Generator
  9. import typing
  10.  
  11.  
  12. class Number:
  13.     """
  14.    Число n вместе с кешированными значениями n^2 и n^4
  15.  
  16.    - n (number) - само число
  17.    - _n2 (number) - n^2, используется для ускорения проверки на простоту
  18.    - _n4 (number) - n^4, используется для ускорения вычислений
  19.    - _log2_n4 (number) - логарифм n^4 по основанию 2, используется для ускорения вычислений
  20.    """
  21.  
  22.     def __init__(self, number: int):
  23.         self.n = number
  24.         self._n2 = number * number
  25.         self._n4 = self._n2 * self._n2
  26.  
  27.         # В решателе используются логарифмы, поэтому кешируем их
  28.         self._log2_n4 = math.log2(self._n4)
  29.  
  30.     def __repr__(self):
  31.         return f"Number({self.n})"
  32.  
  33.  
  34. class Solver:
  35.     """
  36.    Решатель задачи. Находит все числа вида 2^k * p^4, где k - степень двойки, p - простое число в заданном диапазоне.
  37.  
  38.  
  39.    - left (int) - левая граница диапазона (включительно)
  40.    - right (int) - правая граница диапазона (включительно)
  41.    """
  42.  
  43.     PRIMES_100 = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37,
  44.                   41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
  45.  
  46.     def __init__(self, left: int, right: int):
  47.         self.left = min(left, right)
  48.         self.right = max(left, right)
  49.         if self.left < 2:
  50.             raise ValueError("Left bound must be at least 2.")
  51.         # В решателе используются логарифмы, поэтому кешируем их
  52.         self._log2_left = math.log2(self.left)
  53.         self._log2_right = math.log2(self.right)
  54.  
  55.         # Список простых чисел. Нужно обеспечить, что в нём есть все простые числа до \sqrt[4](right)
  56.         # начинаем с простых чисел до 100
  57.         self.primes = list(map(Number, Solver.PRIMES_100))
  58.         # Добавляем простые числа до \sqrt[4](right)
  59.         while self.primes[-1]._n4 < self.right:
  60.             self.add_next_prime()
  61.  
  62.     def add_next_prime(self) -> Number:
  63.         """
  64.        Находит следующее простое число и добавляет его в список простых чисел.
  65.        """
  66.         p = self.primes[-1].n + 2
  67.         while not self.is_prime(p):
  68.             p += 2
  69.         self.primes.append(Number(p))
  70.         return self.primes[-1]
  71.  
  72.     def is_prime(self, n: int) -> bool:
  73.         """
  74.        Проверяет, является ли число простым.
  75.  
  76.        Более точно, проверяет, что среди self.primes нет делителей числа n.
  77.        """
  78.         assert (n >= 2)
  79.         for prime in self.primes:
  80.             if prime._n2 > n:
  81.                 break
  82.             if n % prime.n == 0:
  83.                 return False
  84.         return True
  85.  
  86.     class Result:
  87.         """
  88.        Результат вычислений.
  89.  
  90.        - prime (Number) - простое число.
  91.        - degree (int) - степень двойки
  92.        - value (int) - число вида 2^degree * prime^4
  93.        """
  94.  
  95.         def __init__(self, prime: Number, degree: int):
  96.             self.prime = prime
  97.             self.degree = degree
  98.             self.value = (1 << degree)*self.prime._n4
  99.  
  100.         def __repr__(self):
  101.             return f"Result({self.prime}, {self.degree}, value={self.value})"
  102.  
  103.     def is_proper_num(self, p: Number) -> list[Result]:
  104.         """
  105.        Проверяет, является ли число p подходящим под условия задачи.
  106.  
  107.        Если число не подходит, возвращает пустой список.
  108.        Если число подходит, возвращает список Result(p, k), где k - степень двойки, при которой 2^k * p^4 попадает в заданный диапазон.
  109.        """
  110.         if p._n4 > self.right:
  111.             return []
  112.         if p._n4 > self.left:
  113.             return [Solver.Result(p, 0)]
  114.         # Минимальная степень двойки, при которой 2^k * p^4 >= left
  115.         deg = math.ceil(self._log2_left - p._log2_n4)
  116.         # Максимальная степень двойки, при которой 2^k * p^4 <= right
  117.         lim = math.floor(self._log2_right - p._log2_n4)
  118.  
  119.         if deg > lim:
  120.             # В заданном диапазоне нет чисел вида 2^k * p^4
  121.             return []
  122.         return [Solver.Result(p, d) for d in range(deg, lim + 1)]
  123.  
  124.     def gen_proper_odd_primes(self) -> Generator[Result, None, None]:
  125.         """
  126.        Генератор, который перечисляет Result(p,k), то есть числа вида 2^k * p^4, где k - степень двойки, p - простое число, и 2^k * P^4 находится в заданном диапазоне.
  127.        """
  128.         for p in self.primes[1:]:
  129.             yield from self.is_proper_num(p)
  130.             # Если p^4 > right, то дальше нет смысла проверять
  131.             if p._n4 > self.right:
  132.                 break
  133.  
  134.     def solve(self) -> list[Result]:
  135.         """
  136.        Решает задачу, находя все числа вида 2^k * p^4, где k - степень двойки, p - простое число в заданном диапазоне.
  137.  
  138.        Возвращает список объектов Result, соответствующих найденным простым числам.
  139.        """
  140.         return list(self.gen_proper_odd_primes())
  141.  
  142.  
  143. if __name__ == "__main__":
  144.     left = 35_000_000_000
  145.     right = 40_000_000_000
  146.  
  147. if __name__ == "__main__":
  148.     start_time = time.time()
  149.     solver = Solver(left, right)
  150.     results = solver.solve()
  151.     end_time = time.time()
  152.     print(f"Time taken: {end_time - start_time:.6f} seconds")
  153.  
  154.     for result in results:
  155.         print(result)
  156.     numbers = sorted([r.value for r in results])
  157.     for n in numbers:
  158.         print(n)
  159.     print(f"Found {len(numbers)} results")
  160.  
  161. # Сравнение результатов
  162.  
  163.  
  164. def fox(left, right):
  165.     # Функция: проверка, является ли число простым (достаточно до sqrt)
  166.     def is_prime(v_n):
  167.         if v_n < 2:
  168.             return False
  169.         for v_i in range(2, int(v_n ** 0.5) + 1):
  170.             if v_n % v_i == 0:
  171.                 return False
  172.         return True
  173.  
  174.     # Список для хранения результатов
  175.     lst_results = []
  176.  
  177.     # Перебираем все нечётные простые числа p, такие что p^4 * 2^k попадает в диапазон
  178.     for v_p in range(3, math.ceil(math.pow(right, 0.25)), 2):  # 1000 — с запасом, т.к. 999^4 > 1e12
  179.         if is_prime(v_p):
  180.             v_p4 = v_p ** 4  # Вычисляем p^4
  181.  
  182.             v_n = v_p4
  183.             while v_n <= right:
  184.                 if v_n >= left:
  185.                     # Получаем и сортируем нечётные делители (всегда 5 штук)
  186.                     lst_divs = [1, v_p, v_p**2, v_p**3, v_p**4]
  187.                     lst_results.append((v_n, lst_divs))
  188.                 v_n *= 2  # Умножаем на степень двойки
  189.     return lst_results
  190.  
  191.  
  192. if __name__ == "__main__":
  193.     fox_start_time = time.time()
  194.     fox_results = fox(left, right)
  195.     fox_end_time = time.time()
  196.     print(f"Fox time taken: {fox_end_time - fox_start_time:.6f} seconds")
  197.     for n, divs in sorted(fox_results, key=lambda x: x[0]):
  198.         print(n, divs)
  199.     print(f"Fox found {len(fox_results)} results")
  200.  
  201.  
  202. def volodarski(left, right):
  203.  
  204.     def is_prime(n):
  205.         return all(n % i != 0 for i in range(2, n))
  206.  
  207.     def numbers(m, n):
  208.         i = 3
  209.         while True:
  210.             if is_prime(i):
  211.                 j = i ** 4
  212.                 if n < j:
  213.                     break
  214.                 while j <= n:
  215.                     if m <= j:
  216.                         yield j
  217.                     j *= 2
  218.             i += 2
  219.  
  220.     return list(numbers(left, right))
  221.  
  222.  
  223. def volodarski2(left, right):
  224.     # из решения ru.stackoverflow.com/questions/1276027
  225.     def odd_primes(n):
  226.         sieve = bytearray(n // 2)
  227.         if sieve:
  228.             sieve[0] = 1
  229.             for p in range(3, math.isqrt(n - 1) + 1, 2):
  230.                 for i in range(p * p // 2, n // 2, p):
  231.                     sieve[i] = 1
  232.         return (2 * i + 1 for i, k in enumerate(sieve) if k == 0)
  233.  
  234.     def numbers(m, n):
  235.         e = 1 << m.bit_length()
  236.         for p in odd_primes(math.isqrt(math.isqrt(n)) + 1):
  237.             j = e * p ** 4
  238.             # assert j >= m
  239.             while j >= 2 * m:
  240.                 j //= 2
  241.                 e //= 2
  242.             # assert m <= j <= 2 * m
  243.             while j <= n:
  244.                 yield j
  245.                 j *= 2
  246.     return sorted(numbers(left, right))
  247.  
  248.  
  249. if __name__ == "__main__":
  250.     volodarski_start_time = time.time()
  251.     volodarski_results = volodarski2(left, right)
  252.     volodarski_end_time = time.time()
  253.     print(
  254.         f"Volodarski time taken: {volodarski_end_time - volodarski_start_time:.6f} seconds")
  255.     for n in sorted(volodarski_results):
  256.         print(n)
  257.     print(f"Volodarski found {len(volodarski_results)} results")
  258.     # Сравнение результатов
  259.  
  260. # benchmark
  261.  
  262.  
  263. def dt_n(f, n) -> float:
  264.     """
  265.    Функция для измерения времени выполнения функции f n раз.
  266.    """
  267.     t0 = time.time()
  268.     for _ in range(n):
  269.         f()
  270.     return (time.time() - t0)
  271.  
  272.  
  273. def guess_n(f: Callable, T: float) -> tuple[int, float, int]:
  274.     """
  275.    Функция для оценки количества итераций n, необходимых для достижения времени T.
  276.  
  277.    Возвращает кортеж из трех значений:
  278.    - N (int) - оценка количества итераций,
  279.    - dt (float) - время выполнения функции f во время подбора числа итераций,
  280.    - n (int) - количество итераций при последнем измерении времени.
  281.    """
  282.     # Начинаем с n=1 и увеличиваем его, пока время выполнения не станет больше некоторого порога
  283.     # (например, 100 миллисекунд)
  284.     n = 1
  285.     dt = dt_n(f, n)
  286.  
  287.     while dt < 1e-4:
  288.         n *= 2
  289.         dt = dt_n(f, n)
  290.  
  291.     N = math.floor((T/dt) * n)
  292.     return max(1, N), dt, n
  293.  
  294.  
  295. def benchmark(f: Callable, T: float = 1.0) -> tuple[float, int]:
  296.     """
  297.    Функция для бенчмарка функции f.
  298.    """
  299.     N, dT, n = guess_n(f, T)
  300.     if dT < T/2:
  301.         # Подбор числа итераций занял немного времени, поэтому
  302.         # повторяем измерение времени с большим количеством итераций
  303.         dT = dt_n(f, N)
  304.         # Иногда время оказывается отрицательным, поэтому
  305.         # повторяем измерение времени, пока оно не станет положительным
  306.         while dT < 0:
  307.             dT = dt_n(f, N)
  308.     else:
  309.         # Подбор числа итераций занял много времени, поэтому
  310.         # используем его для оценки времени выполнения функции f
  311.         N = n
  312.     return dT, N
  313.  
  314.  
  315. def benchmark_msg(prefix: str, f: Callable, T: float = 1):
  316.     """
  317.    Функция для бенчмарка функции f с выводом сообщения.
  318.    """
  319.     dT, N = benchmark(f, T)
  320.     print(f"{prefix:16s}: {dT:.6f} seconds for {N} iterations", end="\t")
  321.     dt = dT/N
  322.     # if dt < 1e-4:
  323.     #     print(f"average: {dt*1000:.6f}ms")
  324.     # elif dt < 1:
  325.     #     print(f"average: {dt:.6f}s")
  326.     # else:
  327.     #     print(f"average: {dt/60:.6f} min")
  328.     print(f"average: {dt*1000:.6f}ms")
  329.     return dT, N
  330.  
  331.  
  332. if __name__ == "__main__":
  333.     left_0 = 35_000_000
  334.     right_0 = 40_000_000
  335.     for i in range(0, 20, 3):
  336.         left = left_0 * (10**i)
  337.         right = right_0 * (10**i)
  338.        
  339.         print(f"# Testing with left={left:_}, right={right:_}")
  340.         benchmark_msg("Solver", lambda: Solver(left, right).solve())
  341.         benchmark_msg("Fox", lambda: fox(left, right))
  342.         benchmark_msg("Volodarski2", lambda: volodarski2(left, right))
  343.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement