0

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) 的溢出问题。

4

2 回答 2

4

首先看这篇关于除数函数的维基百科文章。简而言之,如果你有一个数字并且你知道它的质因数,你可以很容易地计算出除数的数量(让 SO 做 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可能吧。如果你碰巧找到了一个简单的公式来回答你的问题 any n,那么n在测试用例中也可以使用大的 s ,对于这种情况,该算法可能会超出时间限制。

于 2013-03-25T14:16:29.763 回答
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)位 - 其中不到一半是尾随零,但即使将数字存储为带有有效数字和指数的浮点数也不足以降低所需的存储空间。

因此,与其存储数字本身,不如考虑素数分解。这也允许更容易计算除数的数量,因为

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

现在,让我们研究F(n)一点的素因数分解。我们从

引理:素数pF(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)是 和 的素因数集合的nF(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) .

素数的条目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,关于3.6 * 10^10更新,这远远超出了允许的时间(0.5 秒)内可以完成的工作。

所以我们需要一种不同的方法。让我们单独看一个素数p,并遵循 中的p指数F(n)

设是的素因数分解中v_p(k)的指数。然后我们有pk

v_p(F(n)) = v_p(n) + v_p(F(n-1)) + v_p(F(n-2))

我们知道v_p(F(k)) = 0k < p所以(假设p不是太小而无法理解发生了什么):

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 < p.

对于 的进一步倍数p,我们在指数中得到另一个斐波那契和,所以

            n/p
v_p(F(n)) =  ∑ Fib(n + 1 - k*p)
            k=1

-- 直到n >= p²,因为指数的倍数贡献了 2,并且相应的被加数必须乘以 2;的倍数,乘以 3 等。

还可以拆分 的高次幂的倍数的贡献p,因此由于它是 的倍数,因此会得到一个斐波那契和数p,一个是 的倍数,一个是 的倍数,等等,产生

              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)是第k-个卢卡斯数,Luc(k) = Fib(k+1) + Fib(k-1)

出于我们的目的,我们只需要 Fibonacci 数模10^9 + 7,然后除法必须用与 的模逆的乘法来代替D(s)

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

于 2013-04-08T02:54:02.617 回答