Je ne sais pas qui prête encore attention à ce fil de discussion, mais je vais quand même le faire.
Tout d'abord, dans la version officielle en lien, la factorielle doit être de 1000 et non de 10000. De plus, lorsque ce problème a été réutilisé dans un autre concours de programmation, la limite de temps était de 3 secondes, et non d'une seconde. Cela fait une énorme différence dans l'effort que vous devez fournir pour obtenir une solution suffisamment rapide.
Deuxièmement, pour les paramètres réels du concours, la solution de Peter est bonne, mais avec une astuce supplémentaire, vous pouvez l'accélérer d'un facteur 5 avec une architecture 32 bits. (Au lieu de travailler avec des chiffres individuels, implémentez la multiplication en base 100000. Puis, à la fin, faites le total des chiffres de chaque superchiffre. Je ne sais pas si vous aviez droit à un bon ordinateur pour le concours, mais j'ai un ordinateur de bureau à la maison qui est à peu près aussi vieux que le concours. L'exemple de code suivant prend 16 millisecondes pour 1000 ! et 2,15 secondes pour 10000 ! Le code ignore également les 0 de queue lorsqu'ils apparaissent, mais cela n'économise que 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 d'un autre facteur appréciable. Avec les méthodes modernes de multiplication des grands nombres, le calcul de n ! ne prend pas un temps quadratique. Au contraire, vous pouvez le faire en O-tilde(n) temps, où le tilde signifie que vous pouvez ajouter des facteurs logarithmiques. Il existe une accélération simple en raison de Karatsuba qui ne ramène pas la complexité temporelle à ce niveau, mais l'améliore tout de même et pourrait permettre d'économiser un autre facteur de 4 ou plus. Pour l'utiliser, vous devez également diviser la factorielle elle-même en plages de taille égale. Vous créez un algorithme récursif prod(k,n) qui multiplie les nombres de k à n par la formule pseudo-code suivante
prod(k,n) = prod(k,floor((k+n)/2))*prod(floor((k+n)/2)+1,n)
Puis vous utilisez Karatsuba pour faire la grande multiplication qui en résulte.
L'algorithme de multiplication de Schonhage-Strassen basé sur la transformée de Fourier est encore meilleur que celui de Karatsuba. Il se trouve que ces deux algorithmes font partie des bibliothèques modernes de grands nombres. Le calcul rapide d'énormes factorielles pourrait être important pour certaines applications de mathématiques pures. Je pense que Schonhage-Strassen est un peu excessif pour un concours de programmation. Karatsuba est vraiment simple et vous pourriez l'imaginer dans une solution A+ au problème.
Une partie de la question posée est une spéculation selon laquelle il existe une simple astuce de théorie des nombres qui change complètement le problème du concours. Par exemple, si la question était de 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 c'est un exercice très facile de voir que c'est 2 lorsque n=3 et sinon 0 lorsque n+1 est composite. Il existe également des variations de ce principe ; 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, ce qui explique pourquoi 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, non modulo quoi que ce soit, les astuces de la théorie des nombres s'épuisent assez rapidement. L'addition des chiffres d'un nombre ne s'accorde pas bien avec l'addition et la multiplication avec des reports. Il est souvent difficile de promettre que les mathématiques n'existent pas pour un algorithme rapide, mais dans ce cas, je ne pense pas qu'il existe de formule connue. Par exemple, je parie que personne ne connaît la somme des chiffres d'une factorielle googol, même si ce n'est qu'un nombre d'environ 100 chiffres.
1 votes
Êtes-vous sûr que ce n'était pas la somme répétée de chiffres, comme 88 -> 8+8=16 -> 7 ? Je peux faire ça en 10 lignes de code.
0 votes
@tom10 : Il est peu probable que ce soit la question ; car la solution serait simplement "si n>=6 return 9 ; else return the nth element of (1, 2, 6, 6, 3)". Cela nécessiterait bien moins de 10 lignes de code :-)
1 votes
@ShrevatsaR, et tous les autres : oui, oui, je me rends compte que ma reformulation en fait une question assez facile pour la plupart des personnes de cette liste, mais cela ne semble pas être une mauvaise question pour un jeune de 16 ans. Et étant donné qu'elle est restée ici sur SO sans réponse pendant plusieurs heures... est-ce que la déclaration initiale semble raisonnable ? Ou est-ce le Putnam des tests d'informatique ?
2 votes
@tom10, j'appellerais cela la "somme de chiffres ultime". Il y a une règle très simple qui
UltimateDigitSum(A * B) = UltimateDigitSum(UltimateDigitSum(A) * UltimateDigitSum(B))
0 votes
@DanThMan cette équation est intéressante. J'ai écrit un code pour cela, et il donne une somme correcte de chiffres répétés pour 10000 ! C'est proche, mais pas ce que dit le problème. Merci pour cette bonne idée.
0 votes
La vraie question ici est de savoir quelle est la complexité en termes de N du calcul de la somme des chiffres de N ! (tout en se rappelant que les opérations de bignum coûtent log(N))
0 votes
Pour revenir sur mon commentaire précédent : la multiplication de deux nombres à k chiffres coûte log(k)log(log(k)) en utilisant fft au lieu de la multiplication longue standard ; cependant, dans la plupart des cas, log(log(t)) est négligeable.
0 votes
@DanThMan : la "règle très simple" est en fait fausse :-) Elle fonctionne lorsque la multiplication n'a jamais de "carry", mais pas autrement. Par exemple, 23*23=529, dont la somme des chiffres est 16 != 5*5. (Ou même simplement 5*5=25, où 7 !=25.)
0 votes
@ShreevatsaR, cela ne fonctionne que pour les ultime la somme des chiffres, ce que j'ai bien précisé dans mon commentaire.
23 * 23 = 529
a la somme ultime des chiffres5 + 2 + 9 = 16 ... 1 + 6 = 7
,23
a la somme ultime des chiffres2 + 3 = 5
et5 * 5 = 25
a la somme ultime des chiffres2 + 5 = 7
. Puisque7 = 7
ça fonctionne parfaitement. Il se trouve que c'est très utile pour la question de @Piligrim.0 votes
J'ai eu ce problème lors d'un entretien d'embauche et on m'a donné une demi-heure pour l'écrire. mes contraintes étaient 1 sec et 1000 seulement donc j'ai juste écrit un script en python en utilisant math.factorial qui selon moi semblait assez rapide mais l'interviewer n'était pas satisfait. puisque aucune des réponses n'est acceptée ici . Je considère que c'est une question ouverte. Un indice ?