任何更快的算法来计算除数的数量

时间:2013-03-25 14:01:00

标签: algorithm math optimization

F系列定义为

F(0) = 1

F(1) = 1

F(i) = i * F(i - 1) * F(i - 2)  for i > 1

任务是找到F(i)

的不同除数的数量

这个问题来自Timus。我尝试了以下Python,但肯定会超出时间限制。这种强力方法不适用于大输入,因为它也会导致整数溢出。

#!/usr/bin/env python

from math import sqrt
n = int(raw_input())

def f(n):
    global arr
    if n == 0:
        return 1
    if n == 1:
        return 1
    a = 1
    b = 1
    for i in xrange(2, n + 1):
        k = i * a * b
        a = b
        b = k
    return b

x = f(n)
cnt = 0
for i in xrange(1, int(sqrt(x)) + 1):
    if x % i == 0:
        if x / i == i:
            cnt += 1
        else:
            cnt += 2
print cnt

任何优化?

修改 我已经尝试了这个建议,并重写了解决方案:(不直接存储F(n)值,而是一系列因子)

#!/usr/bin/env python

#from math import sqrt

T = 10000
primes = range(T)

primes[0] = False
primes[1] = False
primes[2] = True
primes[3] = True

for i in xrange(T):
    if primes[i]:
        j = i + i
        while j < T:
            primes[j] = False
            j += i

p = []
for i in xrange(T):
    if primes[i]:
        p.append(i)

n = int(raw_input())

def f(n):
    global p
    if n == 1:
        return 1
    a = dict()
    b = dict()
    for i in xrange(2, n + 1):
        c = a.copy()
        for y in b.iterkeys():
            if c.has_key(y):
                c[y] += b[y]
            else:
                c[y] = b[y]
        k = i
        for y in p:
            d = 0
            if k % y == 0:
                while k % y == 0:
                    k /= y
                    d += 1
                if c.has_key(y):
                    c[y] += d
                else:
                    c[y] = d
                if k < y: break
        a = b
        b = c
    k = 1
    for i in b.iterkeys():
        k = k * (b[i] + 1) % (1000000007)
    return k

print f(n)

它仍然给TL5,不够快,但这解决了值F(n)的溢出问题。

2 个答案:

答案 0 :(得分:4)

首先看到这个wikipedia article on the divisor function。简而言之,如果你有一个数字并且你知道它的主要因素,你可以很容易地计算除数的数量(得到除数的TeX数学):

$n = \prod_{i=1}^r p_i^{a_i}$

$\sigma_x(n) = \prod_{i=1}^{r} \frac{p_{i}^{(a_{i}+1)x}-1}{p_{i}^x-1}$

无论如何,这是一个简单的功能。

现在,要解决您的问题,请将其保留为一组素数因子和指数大小,而不是将F(n)作为数字本身。然后,计算F(n)的函数只需要F(n-1)F(n-2)的两个集合,对两个集合中相同素数因子的指数求和(对于不存在的集合假设为零)并另外添加数字i的素数因子和指数大小的集合。这意味着您需要另一个简单的 1 函数来查找i的素因子。

以这种方式计算F(n),你只需要将上面的公式(取自维基百科)应用到集合中,这就是你的价值。另请注意,F(n)可能很快变得非常大。这个解决方案也避免使用大数字库(因为没有素数因子也没有指数可能超过40亿 2 )。


1 当然,对于任意大的i来说,这并不是那么简单,否则我们现在就不会有任何形式的安全性,但对于您的应用程序来说,它应该足够简单。

2 好吧。如果您碰巧找出了一个简单的公式来回答任何n给出的问题,那么在测试用例中也可以使用大n个,这个算法很可能会超出时间限制

答案 1 :(得分:3)

这是一个有趣的问题。

F(n)增长极快。自F(n) <= F(n+1) n以来,我们有

F(n+2) > F(n)²

适用于所有n,因此

F(n) > 2^(2^(n/2-1))

代表n > 2。粗略估计已经表明,除了最小的n之外,任何人都无法存储这些数字。由此F(100)需要超过(2^49)位存储,而128 GB仅需2^40位。实际上,F(100)的主要因子分解是

*Fiborial> fiborials !! 100
[(2,464855623252387472061),(3,184754360086075580988),(5,56806012190322167100)
,(7,20444417903078359662),(11,2894612619136622614),(13,1102203323977318975)
,(17,160545601976374531),(19,61312348893415199),(23,8944533909832252),(29,498454445374078)
,(31,190392553955142),(37,10610210054141),(41,1548008760101),(43,591286730489)
,(47,86267571285),(53,4807526976),(59,267914296),(61,102334155),(67,5702887),(71,832040)
,(73,317811),(79,17711),(83,2584),(89,144),(97,3)]

这将需要大约9.6 * 10^20(大约2^70)位 - 稍微不到一半的位是尾随零,但即使存储数字àla浮点数与有效数和指数没有足够的存储空间。

因此,不是自己存储数字,而是可以考虑素数因子分解。这也可以更容易地计算除数的数量,因为

              k                    k
divisors(n) = ∏ (e_i + 1)  if  n = ∏ p_i^e_i
             i=1                  i=1

现在,让我们稍微调查F(n)的主要因素。我们从

开始

引理:当且仅当p时,F(n) p <= n分为F(0) = F(1) = 1

通过归纳很容易证明:<= 1不能被任何素数整除,并且没有素数n > 1

现在假设A(k) = The prime factors of F(k) are exactly the primes <= k

k < n

适用于F(n) = n * F(n-1) * F(n-2) 。然后,因为

F(n)

n的集素数因子是F(n-1)F(n-2)F(k)的素数因子集的并集。

通过归纳假设,P(k) = { p | 1 < p <= k, p prime } 的素数因子集是

k < n

代表n。现在,如果n是复合的,则n的所有素数因子都比F(n)小,因此P(n-1)的素因子集是n,但是P(n) = P(n-1)不是素数,n。另一方面,如果F(n)是素数,那么P(n-1) ∪ {n} = P(n) 的素数因子是

F(n)

有了这个,让我们看看一次跟踪n的素数因子化,并更新每个n的列表/字典是多少工作(我忽略了找到因子分解的问题) n的{​​{1}},对于所涉及的小p而言并不需要很长时间。

n = p的条目n的条目首先显示在N - p + 1,然后针对每个F(N)进行更新,总共为 ∑ (N + 1 - p) = π(N)*(N+1) - ∑ p ≈ N²/(2*log N) p <= N p <= N 创建/更新N = 10^6次1}}。因此有

3.6 * 10^10

总计更新。对于p,大约p次更新,这比允许的时间(0.5秒)更多。

所以我们需要一种不同的方法。让我们单独查看一个素数F(n),并按照v_p(k)p的指数进行操作。

k成为v_p(F(n)) = v_p(n) + v_p(F(n-1)) + v_p(F(n-2)) 的素数因子分解中v_p(F(k)) = 0的指数。然后我们有

k < p

我们知道p的{​​{1}}。所以(假设v_p(F(n)) = v_p(n) + v_p(F(n-1)) + v_p(F(n-2)) v_p(F(p)) = 1 + 0 + 0 = 1 v_p(F(p+1)) = 0 + 1 + 0 = 1 v_p(F(p+2)) = 0 + 1 + 1 = 2 v_p(F(p+3)) = 0 + 2 + 1 = 3 v_p(F(p+4)) = 0 + 3 + 2 = 5 v_p(F(p+5)) = 0 + 5 + 3 = 8 并不太小,无法理解发生了什么):

v_p(F(p+k)) = Fib(k+1)

所以我们得到指数的斐波那契数,p - 暂时,因为后来p的倍数会进一步注入v_p(F(2*p-1)) = 0 + Fib(p-1) + Fib(p-2) = Fib(p) v_p(F(2*p)) = 1 + Fib(p) + Fib(p-1) = 1 + Fib(p+1) v_p(F(2*p+1)) = 0 + (1 + Fib(p+1)) + Fib(p) = 1 + Fib(p+2) v_p(F(2*p+2)) = 0 + (1 + Fib(p+2)) + (1 + Fib(p+1)) = 2 + Fib(p+3) v_p(F(2*p+3)) = 0 + (2 + Fib(p+3)) + (1 + Fib(p+2)) = 3 + Fib(p+4)

2*p

但来自v_p(F(2*p+k)) = Fib(p+k+1) + Fib(k+1)的额外权力也遵循一个不错的斐波那契模式,我们0 <= k < pp

对于 n/p v_p(F(n)) = ∑ Fib(n + 1 - k*p) k=1 的进一步倍数,我们在指数中得到另一个斐波纳契加数,所以

n >= p²

- 直到,因为的倍数对指数贡献两个,相应的加数必须乘以2;对于p的倍数,乘以3等。

还可以分割p的倍数的倍数的贡献,因此它会得到一个斐波那契加数,因为它是的倍数,一个是的倍数。 1}},一个是 n/p n/p² n/p³ v_p(F(n)) = ∑ Fib(n + 1 - k*p) + ∑ Fib(n + 1 - k*p²) + ∑ Fib(n + 1 - k*p³) + ... k=1 k=1 k=1 等的倍数,产生

0 < a <= s

现在,特别是对于较小的素数,这些总和有很多术语,以这种方式计算它们会很慢。幸运的是,对于 m ∑ Fib(a + k*s) = (Fib(a + (m+1)*s) - (-1)^s * Fib(a + m*s) - (-1)^a * Fib(s - a) - Fib(a)) / D(s) k=0

,有一个封闭的斐波那契数和的公式,其指数是一个算术级数。
D(s) = Luc(s) - 1 - (-1)^s

,其中

Luc(k)

kLuc(k) = Fib(k+1) + Fib(k-1) - 卢卡斯数字10^9 + 7

出于我们的目的,我们只需要模数为D(s)的斐波那契数,然后必须用F(n)的模逆,乘以除法。

使用这些事实,10^9+7n <= 10^6的除数的数量可以在{{1}}的允许时间内计算(在我的旧32位框上约为0.06秒),尽管使用Python,在测试机器上,可能需要进一步优化。