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)の2つのセットを取り、両方のセットの同じ素因数の指数を合計し(存在しないものはゼロと仮定)、さらに、数の素因数と指数サイズのセットを追加します。これは、の素因数を見つけるために別の単純な1関数が必要であることを意味します。F(n-1)F(n-2)ii

このように計算F(n)するには、上記の式(Wikipediaから取得)をセットに適用するだけで、価値があります。F(n)また、すぐに非常に大きくなる可能性があることにも注意してください。このソリューションは、大きな数のライブラリの使用も回避します(素因数もその指数も40億を超える可能性がないため2)。


1もちろん、これは任意の大きさiの場合はそれほど単純ではありません。そうでない場合、現時点ではセキュリティの形式はありませんが、アプリケーションの場合は十分に単純である必要があります。

2そうかもしれません。与えられた質問に答える簡単な式をたまたま理解した場合、テストケースでもn大きなnsが発生する可能性があり、このアルゴリズムでは制限時間を超える可能性があります。

于 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)のストレージが必要になり、128GBは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)少しの主な因数分解を調べてみましょう。まずは

補題:素数は、の場合にのみ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)素因数の集合の和集合です。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秒)で実行できるよりもはるかに多くなります。

したがって、別のアプローチが必要です。p1つの素数だけを見て、の指数に従っ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倍など。

の倍数のより高い累乗の寄与を分割することもできるので、1はの倍数であるため、1つはの倍数であるため、1つはの倍数であるため、1pつのフィボナッチ被加数を取得します。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)

私たちの目的のために、モジュラのフィボナッチ数だけが必要であり、10^9 + 7除算はモジュラ逆数の乗算に置き換える必要がありますD(s)

F(n)これらの事実を使用して、モジュロの約数の数を10^9+7許容時間内に計算できますn <= 10^6(私の古い32ビットボックスでは約0.06秒)。ただし、Pythonの場合、テストマシンでは、さらに最適化する必要があります。

于 2013-04-08T02:54:02.617 に答える