乘以数字长度的所有数字分量的总和必须等于相同的数字

Sum of all number components powered to length of the number must equal the same number

符合条件的素数有七个。我需要全部找到,但是程序太慢了,有没有更好的方法?

#edit 刚刚发现那些数字被称为自恋或阿姆斯特朗数字

import math

def prime(n):
    for i in range(2,int(math.sqrt(n))+1):
        if n%i == 0: return False
    
    return True

def sum_powers(n):
    num_str = str(n)
    sum = 0
    for i in num_str:
        sum += int(i)**len(num_str)
    
    return sum
    
def findprimes(desired_amount):
    amount = 0
    number = 2
    while amount < desired_amount:
        if prime(number):
            if number == sum_powers(number):
                print(number)
                amount += 1
                number += 1
            else:
                number += 1
        else:
            number += 1

findprimes(7)

你可能会跳过很多数字,因为除 2 之外的所有素数都是奇数。这意味着您的候选号码必须有奇数个奇数位。此外,候选数字中的数字顺序对得出的幂和并不重要,因此您应该从不同的数字组合开始,并检查结果是否产生相同的数字(在任何排列中)。

我相信通过这种方法,您应该能够对符合上述所有条件的候选人使用素数检查算法,而不是从素数生成器开始。

这是我对这种方法的看法:

  • isPrime(N) 是一个简单的质数检查函数
  • oddDigits(N) 是一个生成器,它将生成 N 个数字的不同组合,这些数字的总和为奇数。
  • match(digits)(也是一个生成器),采用数字组合并确定幂的总和是否达到由相同数字(以任何顺序)组成的数字。这是一个生成器,因为可能有不止一个幂来检查数字组合何时包含零(可能是前导)。
  • 主循环遍历数字组合(最多 20 位的数字只有 5,007,002)并打印出匹配的数字。

...

def isPrime(N):
    if N<3: return N==2
    return all(N%f for f in range(3,int(N**0.5)+1,2))

def oddDigits(size,minDigit=0,digitSum=0):
    for digit in range(minDigit,10):
        if size == 1:
            if (digitSum+digit)%2: yield [digit]
            continue
        for rest in oddDigits(size-1,digit,digitSum+digit):
            yield [digit]+rest
            
def match(digits):
    minLength = len(digits)-digits.count(0)
    for power in range(minLength,len(digits)+1):
        sp = sum(d**power for d in digits)
        if sorted(map(int,str(sp)))!=digits[-power:]:
            continue
        if isPrime(sp):
            yield sp
    
for digits in oddDigits(20):
    for N in match(digits):
        print(N)

实际的 7 个素数一定很大,因为这只能找到其中的 4 个:

3
5
7
28116440335967

希望这能让你走上正轨

暴力破解可行吗?

首先,很明显,如果你想找到所有的解(并确保你有所有可能的解),生成素数是行不通的。让我们看看为什么。

我们想为需要检查的数字找到一些上限。对于给定的位数 n,最大的 n 位数是全 9。该数字还最大化了 d^n 的所有数字 d 的总和,总和为 n*(9^n)。所以要使数次幂之和和原数一样大,我们至少需要n*(9^n) >= 10^(n-1)。这清楚地表明也存在有限界限,因为右侧的增长速度比左侧快得多。

使用 wolfram alpha,f(n) = n*(9^n) - 10^(n - 1) 的最大零在 n=60.8 左右,因此我们可以确定在检查了最多 60 位的所有数字后我们就完成了。这 远远大于 比任何计算过的连续素数列表(似乎都是低于 10^20 左右的素数),所以天真的方法甚至不能被暴力破解超级计算机。


对所有数字多重集进行深度优先搜索

但是,使用我评论中提到的策略,还有更好的方法。我们可以生成所有方法来选择任意数量的数字 0-9 到 60 的多重集:从组合学来看,总共有(60 + 9 选择 9)个选项,大约 6 * 10^10。这更接近于暴力破解,尤其是通过一些巧妙的过滤。仅仅为了找到这七个答案,原来我们只需要检查最多 23 位数字。

一旦你有了 m 个数字的组合,你可以将每个数字的 m 次方相加,看看你是否能得到原来的数字,以及 sum-string 是否具有相同的重数数字作为您的组合。

完整代码如下。我已经包含了我自己的 Miller-Rabin 素数测试,但是使用像 gmpy2 这样的数字库会更快。在我的电脑上,它在大约一分钟内打印出七个素数。我将其与 @Alain T 的解决方案进行了比较,后者在概念上是相似的(但两者都使用 Miller-Rabin 素数测试仪),尽管没有使用他们聪明的 even/odd 过滤,但它的运行速度快了大约 3 倍。


import collections
from time import perf_counter
import math
from typing import List, Tuple

def main():
    
    found_primes = 0

    def solve(my_nums: List[Tuple[int, int]], actual_sum: int) -> None:

        if not is_prime_miller(actual_sum):
            return

        sum_counts = collections.Counter(str(actual_sum))
        for orig_dig, amount_to_use in my_nums:
            if amount_to_use != sum_counts[str(orig_dig)]:
                return

        nonlocal found_primes
        found_primes += 1
        print(f"Found ans number {found_primes}: {actual_sum}")


    def dfs(curr_digit: int, remaining: int, curr_sum: int, orig_num_digs: int, biggest: int,
            used_dig_list: List[Tuple[int, int]]) -> None:

        my_pow = POWS[curr_digit]

        if curr_sum >= biggest or 10 * (remaining * my_pow + curr_sum) < biggest - 10:
            return
        if remaining == 0:
            if len(str(curr_sum)) == orig_num_digs:
                solve(used_dig_list, curr_sum)
            return
        assert remaining > 0
        if curr_digit == 0:
            # curr_sum += remaining*pow(9, orig_num_digs)
            if len(str(curr_sum)) == orig_num_digs:
                solve(used_dig_list + [(0, remaining)], curr_sum)
            return

        new_sum = curr_sum
        for amount_to_use in range(remaining + 1):
            dfs(curr_digit=curr_digit - 1, remaining=remaining - amount_to_use, curr_sum=new_sum,
                orig_num_digs=orig_num_digs, biggest=biggest,
                used_dig_list=used_dig_list + [(curr_digit, amount_to_use)])
            new_sum += my_pow
            if new_sum >= biggest:
                return

    for digits_to_use in range(1, 24):
        print(f"Starting {digits_to_use}, {perf_counter() - start_time :.4f} sec. since start")
        POWS = [pow(x, digits_to_use) for x in range(10)]
        dfs(curr_digit=9, remaining=digits_to_use, curr_sum=0, orig_num_digs=digits_to_use,
            biggest=pow(10, digits_to_use), used_dig_list=[])

def is_prime_miller(n: int) -> bool:
    small_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41]
    if n < 2:
        return False
    for p in small_primes:
        if n < p * p:
            return True
        if n % p == 0:
            return False
    r, s = 0, n - 1
    while s % 2 == 0:
        r += 1
        s //= 2
    for a in small_primes:

        x = pow(a, s, n)
        if x == 1 or x == n - 1:
            continue
        for _ in range(r - 1):
            x = pow(x, 2, n)
            if x == n - 1:
                break
        else:
            return False
    return True

if __name__ == '__main__':
    start_time = perf_counter()
    main()

给出了这个输出:

2
3
5
7
28116440335967
449177399146038697307
35452590104031691935943

编辑:我已经更正了之前的说法,即 10^33 是这些数字的界限,post 提到的称为 'narcissistic numbers':我得出的界限应该是10^60,虽然这样的质数确实只有7个,而且这种最大的合数也不足40位。