新增欧拉第34、35题解法及说明

This commit is contained in:
2026-01-04 22:21:57 +08:00
parent 0be6fed37c
commit 0ea015f3af
5 changed files with 367 additions and 0 deletions

View File

@@ -0,0 +1,206 @@
"""
The number, 179, is called a circular prime because all rotations of the digits: 179, 791, and 917,
are themselves prime.
There are thirteen such primes below 100 : 2,3,5,7,11,13,17,31,37,71,73,79, and 97.
How many circular primes are there below one million?
"""
import random
import time
from itertools import permutations
from sqlite3.dbapi2 import Time
from typing import Union
# 预计算的小素数集合,用于快速排除
_SMALL_PRIMES = frozenset((2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37))
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} time: {end_time - start_time:.6f} seconds")
return result
return wrapper
def sieve_best(n: int) -> list[int]:
"""返回 [2..n) 内所有素数"""
if n <= 2:
return []
size = (n - 1) // 2
comp = bytearray(size)
limit = int(n**0.5) // 2
for i in range(1, limit + 1):
if not comp[i]:
p = 2 * i + 1
s = (p * p - 1) // 2
comp[s::p] = b"\1" * ((size - s - 1) // p + 1)
res = [2, *(2 * i + 1 for i, v in enumerate(comp) if not v)]
res.sort()
return [i for i in res if i != 1]
def is_circular(n: int, primes: list[int]) -> bool:
if n < 2:
return False
if n in [2, 3, 5, 7, 11]:
return True
digits = str(n)
allnum = [digits[i:] + digits[:i] for i in range(len(digits))]
for num in allnum:
if int(num) not in primes:
return False
return True
def is_probable_prime(n: Union[int, str], k: int = 20) -> bool:
"""
Miller-Rabin素性检测算法
参数:
n: 待检测的整数支持int或数字字符串
k: 测试次数误差率约为4^(-k)默认20次误差率约9e-13
返回:
bool: 如果n很可能是素数返回True否则返回False
"""
# 支持字符串输入
if isinstance(n, str):
n = int(n)
# 基本检查
if n < 2:
return False
if n in _SMALL_PRIMES:
return True
# 检查小素数的倍数
for p in _SMALL_PRIMES:
if n % p == 0:
return False
# 特殊情况偶数已排除2
if n % 2 == 0:
return False
# 将 n-1 写成 2^s * d 的形式
d = n - 1
s = 0
while d % 2 == 0:
d //= 2
s += 1
# 边界:如果 n-1 可以直接作为基数,避免随机数生成问题
if n < 4:
return n in (2, 3)
# Miller-Rabin测试
for _ in range(k):
# 生成随机基数,范围 [2, n-2]
a = random.randrange(2, n - 1)
# 计算 x = a^d mod n
x = pow(a, d, n)
# 如果 x == 1 或 x == n-1通过本轮测试
if x == 1 or x == n - 1:
continue
# 重复平方检查
for _ in range(s - 1):
x = pow(x, 2, n)
if x == n - 1:
break
else:
# 所有平方后都没有得到 n-1n是合数
return False
return True
# 优化的确定素数版本(适用于小整数)
def is_prime_small(n: int) -> bool:
"""确定性素数检测适用于n < 2^64"""
if n < 2:
return False
if n % 2 == 0:
return n == 2
if n % 3 == 0:
return n == 3
# 检查小素数
for p in _SMALL_PRIMES:
if n % p == 0:
return n == p
# 6k±1 优化检查
i = 5
w = 2
while i * i <= n:
if n % i == 0:
return False
i += w
w = 6 - w # 在2和4之间切换5,7,11,13,17,19...
return True
# 智能选择函数
def is_prime(n: Union[int, str], k: int = 20) -> bool:
"""
智能素数检测对小整数使用确定性算法对大整数使用Miller-Rabin
参数:
n: 待检测的整数
k: Miller-Rabin测试次数仅对大整数有效
返回:
bool: 如果是素数返回True
"""
if isinstance(n, str):
n = int(n)
# 小整数使用确定性算法
if n < 2**64:
return is_prime_small(n)
# 大整数使用Miller-Rabin
return is_probable_prime(n, k)
@timer
def main_quick(max: int) -> None:
res = set()
for i in range(2, max):
if is_prime(i):
if i in res:
continue
else:
s = str(i)
allnums = [s[i:] + s[:i] for i in range(len(s))]
flag = 0
for j in allnums:
num = int("".join(j))
if is_prime(num):
flag += 1
if flag == len(allnums):
res.update(allnums)
print(len(res))
@timer
def main(n: int) -> None:
primes = sieve_best(n)
circular_primes = [p for p in primes if is_circular(p, primes)]
print(len(circular_primes))
if __name__ == "__main__":
num = 1000000
main(num)
main_quick(num)

View File

@@ -0,0 +1,80 @@
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time:.6f} seconds")
return result
return wrapper
# ---------- 1. PRIMES ----------
MAX = 1_000_000
is_p = bytearray(b"\1") * MAX # 1 表示素数0 表示合数(与原来相反,方便理解)
is_p[0] = is_p[1] = 0 # 0和1不是素数
# 优化:使用标准埃拉托斯特尼筛法
for p in range(2, int(MAX**0.5) + 1):
if is_p[p]:
is_p[p * p : MAX : p] = b"\0" * ((MAX - p * p - 1) // p + 1)
# ---------- 2. SCROLLING ----------
def rotations(n: int) -> list[int]:
"""返回 n 的所有旋转(数学移位版,无字符串开销)"""
if n < 10:
return [n]
s = str(n)
k = len(s)
res = []
for i in range(k):
# 使用切片旋转,避免重复计算
rotated = int(s[i:] + s[:i])
res.append(rotated)
return res
# ---------- 3. MAIN ----------
@timer
def main(limit: int = MAX) -> int:
# 预过滤:只要含 0,2,4,5,6,8 就可以跳过除了2和5本身
bad_digits = set("024568")
total = 0
counted = set() # 用于去重,避免重复计数
for p in range(2, limit):
if is_p[p]: # p是素数
# 快速数字过滤2和5是特例
if p != 2 and p != 5:
s = str(p)
if not bad_digits.isdisjoint(s):
continue
# 如果已经统计过,跳过
if p in counted:
continue
# 检查所有旋转
rs = rotations(p)
# 检查所有旋转数是否都是素数
valid = True
for r in rs:
if r >= limit or not is_p[r]:
valid = False
break
if valid:
# 如果是循环素数,统计并标记已计数
total += len(set(rs)) # 使用set避免重复旋转
counted.update(rs)
return total
if __name__ == "__main__":
print(f"循环素数数量: {main()}")

View File

@@ -0,0 +1,6 @@
# 判断素数
这个问题也很有和判断素数,以及如何更好的记录判断结果。
当然,如何更快计算出滚动的数字也很重要。
个人感觉这是个算法问题更多的问题,数学占比并不多。