web-dev-qa-db-fra.com

Somme des chiffres d'une factorielle

Lien vers le problème initial

Ce n'est pas une question de devoirs. Je pensais juste que quelqu'un pourrait connaître une vraie solution à ce problème.

J'étais sur un concours de programmation en 2004, et il y avait ce problème:

Soit n, trouve la somme des chiffres de n !. n peut être compris entre 0 et 10000. Limite de temps: 1 seconde. Je pense qu'il y avait jusqu'à 100 numéros pour chaque ensemble de test.

Ma solution était assez rapide mais pas assez rapide, alors je la laisse fonctionner pendant un certain temps. Il a construit un tableau de valeurs pré-calculées que je pourrais utiliser dans mon code. C'était un bidouillage, mais cela a fonctionné.

Mais il y a un gars qui a résolu ce problème avec environ 10 lignes de code et qui donnerait une réponse en un rien de temps. Je crois que c'était une sorte de programmation dynamique, ou quelque chose de la théorie des nombres. Nous étions 16 à ce moment-là, donc cela ne devrait pas être une "science de fusée". 

Est-ce que quelqu'un sait quel type d'algorithme il pourrait utiliser?

EDIT: Je suis désolé si je n'ai pas précisé la question. Comme mquander a dit, il devrait y avoir une solution intelligente, sans bug, avec du code Pascal simple, quelques boucles, O (n2) ou quelque chose comme ça. 1 seconde n'est plus une contrainte.

J'ai trouvé ici que si n> 5, alors 9 divise la somme des chiffres d'une factorielle. Nous pouvons également déterminer le nombre de zéros à la fin du nombre. Peut-on utiliser ça?

Ok, un autre problème du concours de programmation de la Russie. Soit 1 <= N <= 2 000 000 000, sortie N! mod (N + 1). Est-ce en quelque sorte lié?

48
Denis Tulskiy

Je ne sais pas qui fait toujours attention à ce fil, mais voici quand même.

Premièrement, dans la version liée à l'apparence officielle, il ne doit s'agir que de 1 000 facteurs, et non de 10 000 facteurs. En outre, lorsque ce problème a été réutilisé dans un autre concours de programmation, le délai était de 3 secondes et non d'une seconde. Cela fait une énorme différence dans la difficulté avec laquelle vous devez travailler pour obtenir une solution suffisamment rapide.

Deuxièmement, en ce qui concerne les paramètres réels du concours, la solution de Peter est la bonne, mais avec une touche supplémentaire, vous pouvez l’accroître de 5 fois avec une architecture 32 bits. (Ou même un facteur 6 si seulement 1000! Est souhaité.) Ainsi, au lieu de travailler avec des chiffres individuels, appliquez la multiplication en base 100000. Puis, à la fin, additionnez les chiffres de chaque super-chiffre. Je ne sais pas à quel point un ordinateur vous a été autorisé à participer au concours, mais mon ordinateur chez moi est à peu près aussi vieux que le concours. L'exemple de code suivant prend 16 millisecondes pour 1 000! et 2,15 secondes pour 10000! Le code ignore également les 0 finaux au fur et à mesure qu'ils apparaissent, mais cela n'enregistre qu'environ 7% du travail.

#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); }

Troisièmement, il existe un moyen étonnant et assez simple d’accélérer le calcul par un autre facteur non négligeable. Avec les méthodes modernes de multiplication de grands nombres, il ne faut pas beaucoup de temps pour calculer n !. Au lieu de cela, vous pouvez le faire en temps O-tilde (n), où le tilde signifie que vous pouvez ajouter des facteurs logarithmiques. Il y a une simple accélération due à Karatsuba qui ne réduit pas la complexité temporelle à cela, mais l'améliore quand même et pourrait économiser un facteur de 4 environ. Pour l'utiliser, vous devez également diviser la factorielle elle-même en plages de tailles égales. Vous faites un algorithme récursif prod (k, n) qui multiplie les nombres de k à n par la formule du pseudocode

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

Ensuite, vous utilisez Karatsuba pour faire la grande multiplication qui en résulte.

Mieux encore que Karatsuba, l’algorithme de multiplication de Schonhage-Strassen est basé sur la transformation de Fourier. Il se trouve que les deux algorithmes font partie de bibliothèques modernes de grands nombres. Le calcul rapide de très grandes factorielles pourrait être important pour certaines applications en mathématiques pures. Je pense que Schonhage-Strassen est excessif pour un concours de programmation. Karatsuba est très simple et vous pouvez l’imaginer avec une solution A + au problème.


Une partie de la question posée est une hypothèse selon laquelle il existe un truc simple en théorie des nombres qui change complètement le problème de la concurrence. Par exemple, si la question devait déterminer n! mod n + 1, alors le théorème de Wilson dit que la réponse est -1 lorsque n + 1 est premier et qu'il est très facile de voir que c'est 2 lorsque n = 3 et sinon 0 lorsque n + 1 est composite. Il y a des variations de cela aussi; par exemple n! est également très prévisible mod 2n + 1. Il existe également des liens entre les congruences et les sommes de chiffres. La somme des chiffres de x mod 9 est également x mod 9, raison pour laquelle la somme est 0 mod 9 lorsque x = n! pour n> = 6. La somme alternée des chiffres de x mod 11 est égale à x mod 11.

Le problème est que si vous voulez la somme des chiffres d’un grand nombre, sans rien modulo, les astuces de la théorie des nombres s’épuisent assez rapidement. L'addition des chiffres d'un nombre ne va pas bien avec l'addition et la multiplication avec des retenues. Il est souvent difficile de promettre que le calcul n'existe pas pour un algorithme rapide, mais dans ce cas, je ne pense pas qu'il existe une formule connue. Par exemple, je parie que personne ne connaît la somme des chiffres d'une factorielle de googol, même s'il ne s'agit que d'un nombre d'environ 100 chiffres.

30
Greg Kuperberg

Ceci est A004152 dans le Encyclopédie en ligne de séquences entières . Malheureusement, il ne dispose d'aucun conseil utile sur la manière de le calculer efficacement. Ses recettes à l'érable et à Mathematica adoptent une approche naïve.

8
Nick Johnson

J'attaquerais le second problème, pour calculer N! mod (N + 1), en utilisant le théorème de Wilson . Cela réduit le problème à vérifier si N est premier.

6
Jitse Niesen

Petit script python rapide disponible à l’adresse http://www.penjuinlabs.com/blog/?p=44 . C'est élégant mais toujours force brute.

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
4
mob

Supposons que vous avez de grands nombres (c'est le moindre de vos problèmes, en supposant que N est vraiment grand, et non pas 10000), et poursuivons à partir de là.

L'astuce ci-dessous est de factoriser N! en factorisant tous n <= N, puis calculez les puissances des facteurs.

Avoir un vecteur de compteurs; un compteur pour chaque nombre premier allant jusqu'à N; réglez-les sur 0. Pour chaque n <= N, factorisez n et augmentez les compteurs de facteurs premiers en conséquence (factorisez intelligemment: commencez par les petits nombres premiers, construisez les nombres premiers en factorisant et rappelez-vous que la division par 2 est un décalage). Soustrayez le compteur de 5 du compteur de 2 et faites le compteur de 5 zéro (personne ne se soucie des facteurs de 10 ici).

calculer tout le nombre premier jusqu'à N, exécuter la boucle suivante

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

Notez que dans le bloc précédent, nous n’utilisions que de (très) petits nombres.

Pour chaque facteur premier P, vous devez calculer P à la puissance du compteur approprié, ce qui prend du temps de log (compteur) en utilisant une quadrature itérative; Maintenant, vous devez multiplier tous ces pouvoirs de nombres premiers.

Dans l’ensemble, vous avez environ N opérations (N) log (N) sur de petits nombres (facteurs premiers log N) et des opérations de Log N Log (Journal N) sur de grands nombres. 

et après l'amélioration du montage, seulement N opérations sur de petits nombres.

HTH

3
David Lehavi

1 seconde? Pourquoi ne pouvez-vous pas simplement calculer n! et additionnez les chiffres? C'est 10000 multiplications et pas plus de quelques dix mille additions, ce qui devrait prendre environ un zillion de seconde.

2
mquander

Vous devez calculer le fatcorial.

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

Si vous souhaitez uniquement calculer la somme des chiffres, vous pouvez ignorer les zéros de fin.

Pour 6! vous pouvez faire 12 x 6 = 72 au lieu de 120 * 6

Pour 7! vous pouvez utiliser (72 * 7) MOD 10

MODIFIER.

J'ai écrit une réponse trop rapidement ...

10 est le résultat de deux nombres premiers 2 et 5.

Chaque fois que vous avez ces 2 facteurs, vous pouvez les ignorer.

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

Le facteur 5 apparaît à 5, 10, 15 ...
Ensuite, un zéro final apparaîtra après avoir été multiplié par 5, 10, 15 ...

Nous avons beaucoup de 2 et 3 ... Nous allons déborder bientôt :-(

Ensuite, vous avez toujours besoin d'une bibliothèque pour les grands nombres.

Je mérite d'être voté!

1
Luc M

une autre solution utilisant 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

Même sans entiers de précision arbitraire, cela devrait être brutal. Dans l'énoncé du problème que vous avez lié à, la plus grande factorielle qu'il faudrait calculer serait 1000 !. C'est un numéro avec environ 2500 chiffres. Alors faites ceci:

  1. Allouez un tableau de 3000 octets, chaque octet représentant un chiffre dans la factorielle. Commencez avec une valeur de 1.
  2. Exécutez la multiplication de niveau sur le tableau à plusieurs reprises, afin de calculer la factorielle.
  3. Somme les chiffres.

Faire les multiplications répétées est la seule étape potentiellement lente, mais je suis certain que 1000 multiplications pourraient être effectuées en une seconde, ce qui est le pire des cas. Sinon, vous pouvez calculer quelques valeurs "jalons" à l'avance et les coller dans votre programme.

Une optimisation potentielle: Éliminer les zéros de fin du tableau lorsqu'ils apparaissent. Ils n'affecteront pas la réponse. 

NOTE ÉVIDUELLE: Je suis une approche de programmation-compétition ici. Vous ne feriez probablement jamais cela dans un travail professionnel.

0
PeterAllenWebb

Voyons voir. Nous savons que le calcul de n! pour tout nombre raisonnablement grand, cela conduira éventuellement à un nombre avec beaucoup de zéros, qui ne contribuent pas à la somme. Que diriez-vous de supprimer les zéros en cours de route? Cela réduirait-il un peu la taille du sizer?

Hmm. Nan. Je viens de vérifier, et le dépassement d'entier est toujours un gros problème, même alors ...

0
Mark Bessey