web-dev-qa-db-ja.com

階乗の桁の合計

元の問題へのリンク

宿題ではありません。私は、誰かがこの問題の実際の解決策を知っているのではないかと思いました。

私は2004年にプログラミングコンテストに参加していて、この問題がありました。

Nを指定して、n!の数字の合計を求めます。 nの範囲は0〜10000です。制限時間:1秒。各テストセットには最大100個の数字があったと思います。

私のソリューションはかなり高速でしたが、十分な速度ではなかったので、しばらく実行させました。これは、コードで使用できる事前計算値の配列を作成しました。それはハックでしたが、うまくいきました。

しかし、約10行のコードでこの問題を解決した人がいたので、すぐに答えが出ます。それはある種の動的プログラミング、あるいは数論からの何かだったと思います。当時は16歳だったので、「ロケットサイエンス」ではありません。

誰かが彼がどのようなアルゴリズムを使用できるか知っていますか?

[〜#〜] edit [〜#〜]:質問を明確にしていないとすみません。 mquanderが言ったように、バグ番号なしで、単純なPascalコード、2つのループ、O(n2)またはそのようなもの。 1秒はもはや制約ではありません。

私は here を見つけました。n> 5の場合、9は階乗の桁の合計を割ります。また、数値の最後にゼロがいくつあるかを見つけることもできます。使えますか?

わかりました、ロシアからのプログラミングコンテストからの別の問題。 1 <= N <= 2 000 000 000の場合、出力N! mod(N + 1)。それはどういうわけか関連していますか?

49
Denis Tulskiy

誰がまだこのスレッドに注意を払っているかはわかりませんが、とにかくここに行きます。

まず、公式に見えるリンクされたバージョンでは、1000階乗である必要があり、10000階乗である必要はありません。また、この問題が別のプログラミングコンテストで再利用された場合、制限時間は1秒ではなく3秒でした。これにより、十分に高速なソリューションを取得するために作業する必要があるかどうかに大きな違いが生じます。

第2に、コンテストの実際のパラメータについては、Peterのソリューションは適切ですが、32ビットアーキテクチャでは、追加の工夫1つで5倍高速化できます。 (または、1000だけが必要な場合は6の係数も!)つまり、個々の桁を処理する代わりに、基数100000で乗算を実行します。最後に、各スーパー桁内の桁を合計します。コンテストで許可されたコンピュータの性能はわかりませんが、自宅と同じくらい古いデスクトップを自宅に持っています。次のサンプルコードは、1000で16ミリ秒かかります。 10000は2.15秒!また、コードは後続の0が表示されるときにそれを無視しますが、作業の約7%しか節約できません。

#include <stdio.h>
int main() {
    unsigned int Dig[10000], first=0, last=0, carry, n, x, sum=0;
    Dig[0] = 1;    
    for(n=2; n <= 9999; n++) {
        carry = 0;
        for(x=first; x <= last; x++) {
            carry = Dig[x]*n + carry;
            Dig[x] = carry%100000;
            if(x == first && !(carry%100000)) first++;
            carry /= 100000; }
        if(carry) Dig[++last] = carry; }
    for(x=first; x <= last; x++)
        sum += Dig[x]%10 + (Dig[x]/10)%10 + (Dig[x]/100)%10 + (Dig[x]/1000)%10
            + (Dig[x]/10000)%10;
    printf("Sum: %d\n",sum); }

第三に、別のかなり大きな要因による計算を高速化する驚くほど簡単な方法があります。大きな数を乗算する最新の方法では、n!の計算に2次時間はかかりません。代わりに、O-tilde(n)時間で実行できます。チルドとは、対数係数を投入できることを意味します。単純な加速があります Karatsubaによる これは、時間の複雑さをその程度まで下げませんが、それでも改善され、さらに4倍程度節約できます。これを使用するには、階乗自体を同じサイズの範囲に分割する必要もあります。擬似コード式でkからnまでの数を乗算する再帰アルゴリズムprod(k、n)を作成します

prod(k,n) = prod(k,floor((k+n)/2))*prod(floor((k+n)/2)+1,n)

次に、結果として発生する大きな乗算を実行するために唐津馬を使用します。

カラツバよりもさらに優れているのは、フーリエ変換ベースのシェーンヘーシュ・ストラセン乗算アルゴリズムです。たまたま、どちらのアルゴリズムも最新の大規模ライブラリの一部です。巨大な階乗をすばやく計算することは、特定の純粋な数学アプリケーションにとって重要になる可能性があります。 Schonhage-Strassenはプログラミングコンテストにはやり過ぎだと思います。 Karatsubaは本当にシンプルで、問題のA +ソリューションで想像できます。


提起された質問の一部は、コンテストの問題を完全に変える単純な数論のトリックがあるという推測です。たとえば、問題がnを決定することだった場合、 mod n + 1の場合、ウィルソンの定理は、n + 1が素数の場合は-1と答え、n = 3の場合は2、n + 1が複合の場合は0であることを確認するのは非常に簡単です。これにもバリエーションがあります。たとえばn!また、非常に予測可能なmod 2n + 1です。合同と数字の合計の間にもいくつかの関連があります。 x mod 9の数字の合計もx mod 9です。そのため、x = nの場合、合計は0 mod 9になります。 n> = 6の場合。xmod 11の数字の交互合計は、x mod 11に等しくなります。

問題は、何も法ではなく大きな数の数字の合計が必要な場合、数論のトリックがすぐになくなることです。数値の数字を合計することは、キャリーを使用した加算および乗算とうまく一致しません。高速なアルゴリズムでは数学が存在しないと約束することはしばしば困難ですが、この場合、既知の公式があるとは思いません。たとえば、グーゴル階乗の数字の合計は、だいたい100桁の数字ですが、だれも知らないのではないでしょうか。

31
Greg Kuperberg

これは、 Integer Sequences のオンライン百科事典の A004152 です。残念ながら、効率的な計算方法に関する有用なヒントはありません。メープルとmathematicaのレシピは単純なアプローチをとっています。

8
Nick Johnson

Nを計算するために、2番目の問題を攻撃します。 mod(N + 1)、 ウィルソンの定理 を使用。これにより、Nが素数であるかどうかをテストする問題が軽減されます。

6
Jitse Niesen

あなたが大きな数を持っていると仮定し(これはあなたの問題の中で最小であり、Nが10000ではなく非常に大きいと仮定)、そこから続けましょう。

以下のトリックはNを因数分解することです!すべてのn <= Nを因数分解して、因数のべき乗を計算します。

カウンターのベクトルを持っています。 Nまでの素数ごとに1つのカウンター。それらを0に設定します。n<= Nごとにnを因数分解し、それに応じて素因数のカウンターを増やします(賢い因数:小さい素数から始め、素因数分解中に素数を作成し、2による除算はシフトであることを覚えておいてください)。 2のカウンターから5のカウンターを減算し、5のカウンターをゼロにします(ここでは、誰も10の係数を気にしません)。

nまでのすべての素数を計算し、次のループを実行します

for (j = 0; j< last_prime; ++j) {
  count[j] = 0;
  for (i = N/ primes[j]; i; i /= primes[j])
    count[j] += i; 
}

前のブロックでは(非常に)小さい数のみを使用したことに注意してください。

素因数Pごとに、Pを適切なカウンターの累乗で計算する必要があります。これには、反復二乗を使用してlog(counter)時間かかります。ここで、これらの素数の累乗をすべて乗算する必要があります。

全体として、小さな数(log N素因数)で約N個のlog(N)演算があり、大きな数でLog N Log(Log N)演算があります。

そして、編集の改善後、少数に対してN回の操作のみが行われました。

HTH

3
David Lehavi

小さくて高速pythonスクリプトは http://www.penjuinlabs.com/blog/?p=44 にあります。エレガントでありながらブルートフォースです。

import sys
for arg in sys.argv[1:]:
    print reduce( lambda x,y: int(x)+int(y), 
          str( reduce( lambda x, y: x*y, range(1,int(arg)))))

$ time python sumoffactorialdigits.py 432 951 5436 606 14 9520
3798
9639
74484
5742
27
141651

real    0m1.252s
user    0m1.108s
sys     0m0.062s
3
mob

1秒?なぜnを計算できないのですか?と数字を合計?これは1万回の乗算であり、わずか数万回の加算であり、約1兆分の1秒かかります。

2
mqp

Fatcorialを計算する必要があります。

1 * 2 * 3 * 4 * 5 = 120。

桁の合計のみを計算する場合は、末尾のゼロを無視できます。

6のために!あなたは120 * 6の代わりに12 x 6 = 72を行うことができます

7人用! (72 * 7)MOD 10を使用できます

編集。

返信が速すぎた...

10は、2つの素数2と5の結果です。

これらの2つの要素があるたびに、それらを無視できます。

1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15...

1   2   3   2   5   2   7   2   3    2   11    2   13    2    3
            2       3       2   3    5         2         7    5
                            2                  3

係数5は5、10、15に表示されます...
その後、5、10、15を乗算すると、末尾のゼロが表示されます...

2と3がたくさんあります...まもなくオーバーフローします:-(

次に、大きな数のライブラリが必要です。

私は反対票を投じられるに値します!

1
Luc M

bigIntegerを使用した別のソリューション

 static long q20(){
    long sum = 0;
    String factorial = factorial(new BigInteger("100")).toString();
    for(int i=0;i<factorial.length();i++){
        sum += Long.parseLong(factorial.charAt(i)+"");
    }
    return sum;
}
static BigInteger factorial(BigInteger n){
    BigInteger one = new BigInteger("1");
    if(n.equals(one)) return one;
    return n.multiply(factorial(n.subtract(one)));
}
0
Ashkan Paya

任意精度の整数がなくても、これはブルートフォース可能なはずです。リンクした問題ステートメントで、計算する必要がある最大の階乗は1000!です。約2500桁の数字です。だからこれをしてください:

  1. 3000バイトの配列を割り当てます。各バイトは、階乗の1桁を表します。値1から始めます。
  2. 階乗を計算するために、配列で小学校の乗算を繰り返し実行します。
  3. 数字を合計します。

乗算を繰り返すことが唯一の潜在的に遅いステップですが、1000回の乗算が1秒間に実行できると確信しています。これは最悪の場合です。そうでない場合は、いくつかの「マイルストーン」値を事前に計算し、それらをプログラムに貼り付けるだけです。

潜在的な最適化の1つは、後続のゼロが出現したときに配列から削除することです。彼らは答えに影響を与えません。

OBVIOUS NOTE:ここではプログラミング競争のアプローチを取っています。あなたはおそらくこれを専門家の仕事では決してしないでしょう。

0
PeterAllenWebb

どれどれ。私たちはnの計算を知っています!かなり大きな数の場合、最終的には末尾にゼロが多く含まれる数になり、合計には寄与しません。途中でゼロを一掃してみませんか?それは数のサイザーを少し小さくしますか?

うーん。いいえ。チェックしたところ、それでも整数オーバーフローは大きな問題です...

0
Mark Bessey