如何更快地生成自恋数字?

时间:2013-01-03 14:36:05

标签: c++ algorithm

“自恋数字”是n位数字,其数字的所有n次方的总和等于数字。

因此,153是一个自恋的数字,因为1^3 + 5^3 + 3^3 = 153

现在给定N,找到N个数字长度的所有自恋数字?

我的方法:是迭代所有数字的数字之和

并检查它是否是相同的数字,并且我计算了权力。

但这还不够好,有没有更快的方法?!

更新 在自然界中,只有88个自恋数字,最大的是39位数, 但我只需要长度为12或更短的数字。

我的代码:

long long int powers[11][12];
// powers[x][y] is x^y. and its already calculated

bool isNarcissistic(long long int x,int n){
    long long int r = x;
    long long int sum = 0;

    for(int i=0; i<n ; ++i){
        sum += powers[x%10][n];
        if(sum > r)
            return false;
        x /= 10;
    }
    return (sum == r);
}

void find(int n,vector<long long int> &vv){
    long long int start = powers[10][n-1];
    long long int end = powers[10][n];

    for(long long int i=start ; i<end ; ++i){
        if(isNarcissistic(i,n))
            vv.push_back(i);
    }
}

9 个答案:

答案 0 :(得分:22)

由于总共只有88个narcisstic数字,你可以将它们存储在一个查找表中并迭代它:http://mathworld.wolfram.com/NarcissisticNumber.html

答案 1 :(得分:14)

从另一端开始。迭代d个数字的所有非递减序列的集合,计算d次幂的总和,并检查是否产生(排序后)你开始的序列。

因为有

9×10 ^(d-1)

d - 数字,但仅限

(10+d-1) `choose` d

非递减的d数字序列,会将搜索空间缩小一个接近d!的因子。

答案 2 :(得分:5)

下面的代码实现了@Daniel Fischer的想法。它会复制table referenced at Mathworld,然后再打印一些11位数字,并确认没有任何12位数字,如here所述。

实际上,生成非增加数字字符串的所有可能的直方图而不是字符串本身实际上更简单,可能更快一些。通过直方图,我的意思是指示相应数字的频率的0-9的表。这些可以直接比较而无需排序。但是下面的代码在&lt; 1秒,所以我不打算实现直方图的想法。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_DIGITS 12

// pwr[d][n] is d^n
long long pwr[10][MAX_DIGITS + 1];

// Digits and final index of number being considered.
int digits[MAX_DIGITS];
int m;

// Fill pwr.
void fill_tbls(void)
{
  for (int d = 0; d < 10; d++) {
    pwr[d][0] = 1;
    for (int p = 1; p <= MAX_DIGITS; p++) 
      pwr[d][p] = pwr[d][p-1] * d;
  }
}

// qsort comparison for integers descending
int cmp_ints_desc(const void *vpa, const void *vpb)
{
  const int *pa = vpa, *pb = vpb;
  return *pb - *pa;
}

// Test current number and print if narcissistic.
void test(void)
{
  long long sum = 0;
  for (int i = 0; i <= m; i++)
    sum += pwr[digits[i]][m + 1];
  int sum_digits[MAX_DIGITS * 2];
  int n = 0;
  for (long long s = sum; s; s /= 10)
    sum_digits[n++] = s % 10;
  if (n == m + 1) {
    qsort(sum_digits, n, sizeof(int), cmp_ints_desc);
    if (memcmp(sum_digits, digits, n * sizeof(int)) == 0) 
      printf("%lld\n", sum);
  }
}

// Recursive generator of non-increasing digit strings.
// Calls test when a string is complete.
void gen(int i, int min, int max)
{
  if (i > m) 
    test();
  else {
    for (int d = min; d <= max; d++) {
      digits[i] = d;
      gen(i + 1, 0, d); 
    }
  }
}

// Fill tables and generate.
int main(void)
{
  fill_tbls();
  for (m = 0; m < MAX_DIGITS; m++)
    gen(0, 1, 9);
  return 0;
}

答案 3 :(得分:2)

我在Lua写了一个程序,它在30829.642秒内找到了所有自恋数字。该程序的基础是递归的数字值计数数组生成器函数,当它生成所有数字值的数字值计数时调用检查函数。每个嵌套循环迭代:

FROM i = 0中的较大值和x的解x + d * o +(s-x)*(d-1)^ o> = 10 ^(o-1) 哪里   - 'a'是到目前为止数字的累计和数,   - 'd'是当前数字值(基数为10的0-9),   - 'o'是总位数(数字值计数数组的总和必须加起来),   - 's'表示在阵列添加到'o'

之前剩余的可用插槽

UP to i&lt; = 's'中的较小者和a + x * d ^ o <1的解决方案。对于具有相同变量的x,10 ^ o。

这确保了所检查的数字总是与“o”具有相同的位数,因此在避免不必要的计算时更可能是自恋。

在循环中,它执行递归调用,递减调用数字值'd'将添加当前数字值的贡献(a = a + i * d ^ o)并使i数字插槽用完远离's'。

我写的主要内容是:

local function search(o,d,s,a,...) --Original number of digits, digit-value, remaining digits, accumulative sum, number of each digit-value in descending order (such as 5 nines)
    if d>0 then
        local d0,d1=d^o,(d-1)^o
        local dd=d0-d1
        --a+x*d^o+(s-x)*(d-1)^o >= 10^(o-1) , a+x*d^o < 10^o
        for i=max(0,floor((10^(o-1)-s*d1-a)/dd)),min(s,ceil((10^o-a)/dd)-1) do
            search(o,d-1,s-i,a+i*d0,i,...) --The digit counts are passed down as extra arguments.
        end
    else
        --Check, with the count of zeroes set to 's', if the sum 'a' has the same count of each digit-value as the list specifies, and if so, add it to a list of narcissists.
    end
end

local digits=1 --Skip the trivial single digit narcissistic numbers.
while #found<89 do
    digits=digits+1
    search(digits,9,digits,0)
end
编辑:我忘了提到我的程序找到了89个自恋数字!这些是它发现的:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407, 1634, 8208, 9474, 54748, 92727, 93084, 548834, 1741725, 4210818, 9800817, 9926315, 24678050, 24678051, 88593477, 146511208, 472335975, 534494836, 912985153, 4679307774, 32164049650, 32164049651, 40028394225, 42678290603, 44708635679, 49388550606, 82693916578, 94204591914, 28116440335967, 4338281769391370, 4338281769391371, 21897142587612075, 35641594208964132, 35875699062250035, 1517841543307505039, 3289582984443187032, 4498128791164624869, 4929273885928088826, 63105425988599693916, 128468643043731391252,449177399146038697307, 21887696841122916288858, 27879694893054074471405, 27907865009977052567814, 28361281321319229463398, 35452590104031691935943, 174088005938065293023722, 188451485447897896036875, 239313664430041569350093, 1550475334214501539088894, 1553242162893771850669378, 3706907995955475988644380, 3706907995955475988644381, 4422095118095899619457938, 121204998563613372405438066, 121270696006801314328439376, 128851796696487777842012787, 174650464499531377631639254, 177265453171792792366489765, 14607640612971980372614873089, 19008174136254279995012734740, 19008174136254279995012734741, 23866716435523975980390369295, 1145037275765491025924292050346, 1927890457142960697580636236639, 2309092682616190307509695338915, 17333509997782249308725103962772, 186709961001538790100634132976990, 186709961001538790100634132976991, 1122763285329372541592822900204593, 12639369517103790328947807201478392, 12679937780272278566303885594196922, 1219167219625434121569735803609966019, 12815792078366059955099770545296129367, 115132219018763992565095597973971522400, 115132219018763992565095597973971522401

答案 4 :(得分:2)

对于后人;-)这与@Krakow10的方法最相似,递归生成数字包,从9开始,然后是8,然后是7 ......到0。

它是Python3代码,在不到10分钟的时间内(在我的方框中)找到所有基数为10的解决方案,其中包含1到61位数字(第一个“显然不可能”的宽度)。这是迄今为止我听过的最快的代码问题。有什么诀窍?没有技巧 - 只是乏味;-)随着我们的进展,到目前为止,部分和产生了对可行延续的约束世界。代码只关注那些,因此能够尽早切断大量搜索。

注意:这没有找到0.我不在乎。虽然所有参考文献都说有88种解决方案,但它们的表都有89个条目。一些急切的编辑器必须稍后添加“0”,然后其他人盲目地复制它; - )

编辑新版本的速度提高了两倍,通过在搜索前面利用一些部分和限制 - 现在在我的盒子上完成了4分多钟。

def nar(width):
    from decimal import Decimal as D
    import decimal
    decimal.getcontext().prec = width + 10
    if width * 9**width < 10**(width - 1):
        raise ValueError("impossible at %d" % width)
    pows = [D(i) ** width for i in range(10)]
    mintotal, maxtotal = D(10)**(width - 1), D(10)**width - 1

    def extend(d, todo, total):
        # assert d > 0
        powd = pows[d]
        d1 = d-1
        powd1 = pows[d1]
        L = total + powd1 * todo # largest possible taking no d's
        dL = powd - powd1  # the change to L when i goes up 1
        for i in range(todo + 1):
            if i:
                total += powd
                todo -= 1
                L += dL
                digitcount[d] += 1
            if total > maxtotal:
                break
            if L < mintotal:
                continue
            if total < mintotal or L > maxtotal:
                yield from extend(d1, todo, total)
                continue
            # assert mintotal <= total <= L <= maxtotal
            t1 = total.as_tuple().digits
            t2 = L.as_tuple().digits
            # assert len(t1) == len(t2) == width
            # Every possible continuation has sum between total and
            # L, and has a full-width sum.  So if total and L have
            # some identical leading digits, a solution must include
            # all such leading digits.  Count them.
            c = [0] * 10
            for a, b in zip(t1, t2):
                if a == b:
                    c[a] += 1
                else:
                    break
            else:  # the tuples are identical
                # assert d == 1 or todo == 0
                # assert total == L
                # This is the only sum we can get - no point to
                # recursing.  It's a solution iff each digit has been
                # picked exactly as many times as it appears in the
                # sum.
                # If todo is 0, we've picked all the digits.
                # Else todo > 0, and d must be 1:  all remaining
                # digits must be 0.
                digitcount[0] += todo
                # assert sum(c) == sum(digitcount) == width
                if digitcount == c:
                    yield total
                digitcount[0] -= todo
                continue
            # The tuples aren't identical, but may have leading digits
            # in common.  If, e.g., "9892" is a common prefix, then to
            # get a solution we must pick at least one 8, at least two
            # 9s, and at least one 2.
            if any(digitcount[j] < c[j] for j in range(d, 10)):
                # we're done picking digits >= d, but don't have
                # enough of them
                continue
            # for digits < d, force as many as we need for the prefix
            newtodo, newtotal = todo, total
            added = []
            for j in range(d):
                need = c[j] - digitcount[j]
                # assert need >= 0
                if need:
                    newtodo -= need
                    added.append((j, need))
            if newtodo < 0:
                continue
            for j, need in added:
                newtotal += pows[j] * need
                digitcount[j] += need
            yield from extend(d1, newtodo, newtotal)
            for j, need in added:
                digitcount[j] -= need
        digitcount[d] -= i

    digitcount = [0] * 10
    yield from extend(9, width, D(0))
    assert all(i == 0 for i in digitcount)

if __name__ == "__main__":
    from datetime import datetime
    start_t = datetime.now()
    width = total = 0
    while True:
        this_t = datetime.now()
        width += 1
        print("\nwidth", width)
        for t in nar(width):
            print("   ", t)
            total += 1
        end_t = datetime.now()
        print(end_t - this_t, end_t - start_t, total)

答案 5 :(得分:1)

我认为这个想法是产生类似的数字。例如,61就像你只是求和那样是16相似

  

6 ^ n + 1 ^ n

所以

  

6 ^ N + 1 ^ n = 1的^ N + 6 ^ N

通过这种方式,您可以减少大量的数字。例如,在3位数字情景中,

  

121 == 112 == 211

你明白了。您需要先生成这些数字。 而且你需要生成这些数字而不实际从0-n迭代。

答案 6 :(得分:1)

Python版本是:

def generate_power_list(power):
return [i**power for i in range(0,10)]


def find_narcissistic_numbers_naive(min_length, max_length):
for number_length in range(min_length, max_length):

    power_dict = generate_power_dictionary(number_length)

    max_number = 10 ** number_length
    number = 10** (number_length -1)
    while number < max_number:

        value = 0
        for digit in str(number):
            value += power_dict[digit]

        if value == number:
            logging.debug('narcissistic %s ' % number)

        number += 1

递归解决方案:

在此解决方案中,每个递归处理正在使用的数字数组的单个数字,并尝试该数字的所有适当组合

def execute_recursive(digits, number_length):
index = len(digits)
if digits:
    number = digits[-1]
else:
    number = 0
results = []
digits.append(number)    

if len(digits) < number_length:
    while number < 10:
        results += execute_recursive(digits[:], number_length)
        number += 1
        digits[index] = number

else:
    while number < 10:
        digit_value = sum_digits(digits)
        if check_numbers_match_group(digit_value, digits):
            results.append(digit_value)
            logging.debug(digit_value)

        number += 1
        digits[index] = number

return results


def find_narcissistic_numbers(min_length, max_length):
for number_length in range(min_length, max_length):
    digits = []
    t_start = time.clock()
    results = execute_recursive(digits, number_length)
    print 'duration: %s for number length: %s' %(time.clock() - t_start, number_length)

自恋数字检查 在基本版本中,当检查数字与数字匹配时,我们遍历每个数字类型,以确保每种类型的数量相同。在这个版本中,我们添加了在进行全面检查之前检查数字长度是否正确的优化。

我预计这会对小数字长度产生更大的影响,因为随着数字长度的增加,在分布的中间往往会有更多的数字。结果有点支持这一点:

  1. n = 16:11.5%的改善
  2. n = 19:9.8%的改善
  3. def check_numbers_match_group(number, digits):
    number_search = str(number)
    
    # new in v1.3
    if len(number_search) != len(digits):
        return False
    
    for digit in digit_list:
        if number_search.count(digit[0]) != digits.count(digit[1]):
            return False
    
    return True
    

答案 7 :(得分:0)

我认为如果是自恋号码,你可以使用Multinomial theorem进行一些优化解决方案。
你可以计算(a + b + c + ..)^ n-非n次幂值之和 例如,对于n = 2,你应该比较x和(a + b)^ 2-2 * a * b其中a和b是数字x的数字

答案 8 :(得分:-1)

'''We can use Nar() function to calculate the Narcissitic Number.'''

import math
def Nar(num):
   sum=0
   n=len(str(num))
   while n>0:
     temp=num%10
     sum=sum+math.pow(temp,n)
     num=num/10
   return sum
x=input()
y=Nar(x)
if x==y:
  print x,' is a Narcissistic number'
else:
 print x,' is not a Narcissistic number'