1
votes

Comment puis-je améliorer mon programme de nombres premiers avec l'algorithme Sieve of Eratosthenes?

Mon programme imprime tous les nombres premiers de cette expression:

((1 + sin (0.1 * i)) * k) + 1, i = 1, 2, ..., N. code>

Format d'entrée:

Pas plus de 100 exemples. Chaque exemple a 2 entiers positifs sur la même ligne.

Format de sortie:

Imprimez chaque numéro sur une ligne distincte.

Exemple d'entrée:

4 10

500 100

Exemple de sortie:

5

17

Mais mon algorithme n'est pas assez efficace. Comment puis-je ajouter Sieve of Eratosthenes pour qu'il soit suffisamment efficace pour ne pas afficher "Terminé en raison du délai d'attente".

#include <iostream>
#include <cmath>
using namespace std;

int main() {
long long k, n;
int j;
    while (cin >> k >> n) {
    if (n>1000 && k>1000000000000000000) continue;

    int count = 0;
    for (int i = 1; i <= n; i++) {
        int res = ((1 + sin(0.1*i)) * k) + 1;
        for (j = 2; j < res; j++) {
            if (res % j == 0) break;
        }
        if (j == res) count++;
    }
    cout << count << endl;

}
system("pause");


9 commentaires

Je pense que vous devriez migrer votre question dans codereview.stackexchange.com


ai-je bien compris que vous devez compter le nombre de nombres premiers parmi les nombres r = ((1 + sin (0.1 * i)) * k) +1 pour i = 1 .. n et pour un ensemble de 100 paires maximum (n, k) ?


r sera le résultat numérique de l'expression. Vérifiez ensuite s'il s'agit d'un premier, ignorez l'ensemble de 100 paires maximum.


Dans votre code, quelle est la taille typique de res ?


À partir de l'entrée d'échantillon 4 10 , les nombres res seront 5 5 6 6 6 7 7 7 8 8 Count sera alors 5


res ne devrait-il pas être un long long ?


@Damien Il affichera toujours "Terminé en raison du délai d'attente"


Avant d'essayer d'améliorer la vitesse, nous devons nous assurer que le programme fournit la sortie correcte. C'est pourquoi j'ai mentionné ce problème. Avec un gros k et int , vous pouvez obtenir un débordement


Puisque sin (x) n'est, en général, pas un entier, il faut spécifier ce qu'il faut faire pour le sin (x) non intégral. Certaines options sont plancher, plafond, tronquer vers 0 et arrondir (avec des règles d'arrondi). Il faut faire attention, car sin () prend des valeurs négatives.


3 Réponses :


1
votes

EDIT: j'ai ajouté un troisième moyen d'améliorer l'efficacité
EDIT2: Ajout d'une explication pour laquelle Sieve ne devrait pas être la solution et de certaines relations trigonométriques. De plus, j'ai ajouté une note sur l'historique de la question

Votre problème n'est pas de compter tous les nombres premiers dans une plage donnée, mais seulement ceux qui sont générés par votre fonction.

Par conséquent, je ne pense pas que le tamis d'Eratosthène soit la solution pour cet exercice particulier, pour la raison suivante: n est toujours assez petit alors que k peut être très grand. Si k est très grand, alors l'algorithme de Sieve devrait générer un grand nombre de nombres premiers, pour finalement l'utiliser pour un petit nombre de candidats.

Vous pouvez améliorer l'efficacité de votre programme de trois manières:

  • Évitez de calculer sin (.) à chaque fois. Vous pouvez utiliser des relations trigonométriques par exemple. De plus, la première fois que vous calculez ces valeurs, stockez-les dans un tableau et réutilisez ces valeurs. Le calcul de sin () prend beaucoup de temps
  • Dans votre test pour vérifier si un nombre est premier, limitez la recherche à sqrt (res) . De plus, envisagez de faire le test avec un j impair uniquement, plus 2
  • Si un candidat res est égal au précédent, évitez de refaire le test

Quelques trigonométrie
Si c = cos (0,1) et s = sin (0,1), vous pouvez utiliser les relations:

  • sin (0,1 (i + 1)) = s * cos (0,1 * i) + c * sin (0,1 * i))
  • cos (0,1 (i + 1)) = c * cos (0,1 * i) - s * sin (0,1 * i))

Si n était grand, il devrait être nécessaire de recalculer régulièrement le sin () par la fonction pour éviter de trop calculer les erreurs d'arrondi. Mais cela ne devrait pas être le cas ici car n est toujours assez petit.

Cependant, comme je l'ai mentionné, il est préférable de n'utiliser que l'astuce de "mémorisation" dans un premier temps et de vérifier si cela suffit.

Une note sur l'historique de cette question et pourquoi cette réponse:

Récemment, ce site a reçu plusieurs questions "comment améliorer mon programme, pour compter le nombre de nombres premiers générés par cette fonction k * sin () ..." A ma connaissance, ces questions étaient tous fermés comme duplicata, sous prétexte que le tamis est la solution et a été expliqué dans une précédente question similaire (mais légèrement différente). Maintenant, la même question est réapparue sous une forme légèrement différente "Comment puis-je insérer l'algorithme Sieve dans ce programme ... (avec k * sin () à nouveau)". Et puis j'ai réalisé que le tamis n'était pas la solution. Ce n'est pas une critique des clôtures précédentes car j'ai fait la même erreur dans la compréhension de la question. Cependant, je pense qu'il est temps de proposer une nouvelle solution, même si elle ne correspond pas parfaitement à la nouvelle question


4 commentaires

Le truc c'est que je dois utiliser sin avec l'expression ((1 + sin (0.1 * i)) * k) + 1


Je sais. Mais en utilisant des fonctions trigonométriques, vous pouvez calculer sin (0,1 * (i + 1)) à partir de sin (0,1 * i) . Le problème n'est pas d'éviter le calcul des valeurs sinusales, mais d'éviter l'appel de la fonction sin (.) autant que possible


Quoi qu'il en soit, dans un premier temps, vous pouvez essayer d'utiliser l'astuce de mémorisation et voir quel gain vous obtenez


Je ne comprends pas comment je peux éviter sin (.) à chaque fois.



1
votes

Lorsque vous utilisez une simple Factorisation de la roue , vous pouvez obtenir une très belle accélération de votre code. La factorisation de roue d'ordre 2 utilise le fait que tous les nombres premiers supérieurs à 3 peuvent être écrits comme 6n + 1 ou 6n + 5 pour naturel n em >. Cela signifie que vous n'avez qu'à faire 2 divisions pour 6 numéros. Ou encore plus, tous les nombres premiers plus grands que 5 peuvent être écrits comme 30n + m , avec m dans {1,7,11,13,17,19, 23,29} . (8 divisions pour 30 nombres).

En utilisant ce principe simple, vous pouvez écrire la fonction suivante pour tester vos nombres premiers (roue {2,3}):

% time echo "6000 100000000" | ./a.out                                                                                                                                                                                                        
12999811
echo "6000 100000000"  0.00s user 0.00s system 51% cpu 0.002 total
./a.out  10.12s user 0.00s system 99% cpu 10.124 total

Vous pouvez adapter ce qui précède pour la roue {2,3,5}. Cette fonction peut être utilisée dans le programme principal comme:

 % time echo "6000 100000000" | ./a.out 
 12999811
 echo "6000 100000000"  0.00s user 0.00s system 48% cpu 0.002 total
 ./a.out  209.66s user 0.00s system 99% cpu 3:29.70 total

Un simple timing me donne le code original ( g ++ prime.cpp ) p>

int main() {
  long long k, n;

  while (cin >> k >> n) {
    if (n>1000 && k>1000000000000000000) continue;
    int count = 0;
    for (int i = 1; i <= n; i++) {
      long long res = ((1 + sin(0.1*i)) * k) + 1;
      if (isPrime(res)) { count++; }
    }
    cout << count << endl;
  }
  return 0;
}

alors que la version optimisée me donne

bool isPrime(long long num) {
  if (num == 1)     return false;   // 1 is not prime
  if (num  < 4)     return true;    // 2 and 3 are prime
  if (num % 2 == 0) return false;   // divisible by 2
  if (num % 3 == 0) return false;   // divisible by 3
  int w = 5;
  while (w*w <= num) {
      if(num % w     == 0) return false; // not prime
      if(num % (w+2) == 0) return false; // not prime
      w += 6;
  }
  return true;                     // must be prime
}

D'autres améliorations peuvent être apportées mais pourraient avoir des effets mineurs:

  • précalculez votre table sinusoïdale sin (0.1 * i) pour i de 0 à 1000. Cela évitera de recalculer ces sinus encore et encore. Cependant, cela a un impact mineur car la plupart du temps est gaspillé sur le primetest.
  • Vérifier si res (i) == res (i + 1) : cela n'a pratiquement aucun impact car, selon n et k code > la plupart des res consécutifs ne sont pas égaux.
  • Utilisez une table de recherche, cela peut être plus pratique, cela a un impact.
  • réponse originale:

    Ma suggestion est la suivante:

    1. Précalculez votre sinusable sin (0.1 * i) pour i de 0 à 1000. Cela évitera de recalculer ces sinus encore et encore. Faites-le aussi intelligemment (voir le point 3)
    2. Trouvez la plus grande valeur possible de res qui est res_max=(2*k)+1
    3. Trouvez tous les nombres premiers pour res_max en utilisant le Tamis d'Ératosthène . Sachez également que tous les nombres premiers supérieurs à 3 peuvent être écrits sous la forme 6n + 1 ou 6n + 5 pour un n naturel. Ou encore plus, tous les nombres premiers plus grands que 5 peuvent être écrits comme 30n + m , avec m dans {1,7,11,13,17,19, 23,29} . C'est ce qu'on appelle la Factorisation de la roue . Alors ne prenez pas la peine de vérifier un autre numéro. (un peu plus d'infos ici )

    4. Ayez une table de recherche qui indique si un nombre est un nombre premier.

    5. Faites tout votre boucle sur la table de recherche.


    7 commentaires

    Pour les grands k et petits n , cela peut encore être inefficace, car la plupart des entrées de la table de recherche pour les nombres premiers ne sont jamais utilisées. Au lieu de cette table de recherche et de ce tamis d'Eratosthène, vous pouvez utiliser un test de primalité , bien que cela puisse être trop avancé pour votre problème.


    J'ai fait une fonction similaire à la vôtre, mais cela donne toujours "Terminé en raison du délai d'attente".


    @Erj pouvez-vous me donner l'entrée pour ce délai?


    @kvantour est caché du hackerrank.


    Le programme de @ cdlane fonctionne. Merci pour tous ceux qui ont aidé.


    @Erj, pourriez-vous me dire pourquoi cela n'a pas fonctionné, ou m'indiquer la page du problème?


    @kvantour hackerrank.com/contests/cscb300-2018-t01/challenges C'est la tâche. C'est en bulgare. Faites-moi savoir si vous avez besoin d'une traduction. Je ne peux pas dire pourquoi le vôtre n'a pas fonctionné. Mon professeur a dit qu'il ne donnerait aucun conseil ou indice. Modifié: le problème est 2b . Vous devez vous inscrire avant cela.



    1
    votes

    Vous pouvez améliorer votre vitesse de 10 fois simplement en faisant un meilleur travail avec votre division d'essai. Vous testez tous les nombres entiers de 2 à res au lieu de traiter 2 comme un cas particulier et de tester uniquement les nombres impairs de 3 à la racine carrée de res :

    // k <= 10^3, n <= 10^9
    
    int main() {
        unsigned k;
        unsigned long long n;
    
        while (cin >> k >> n) {
    
            unsigned count = 0;
    
            for (unsigned long long i = 1; i <= n; i++) {
                unsigned long long j, res = (1 + sin(0.1 * i)) * k + 1;
    
                bool is_prime = true;
    
                if (res <= 2 || res % 2 == 0) {
                    is_prime = (res == 2);
                } else {
                    for (j = 3; j * j <= res; j += 2) {
                        if (res % j == 0) {
                            is_prime = false;
                            break;
                        }
                    }
                }
    
                if (is_prime) {
                    count++;
                }
            }
    
        cout << count << endl;
        }
    }
    

    Bien que k = 500 et n = 500000000 va encore prendre une quarantaine de secondes.


    4 commentaires

    Votre code fonctionne avec (k <= 10 ^ 18, N <= 10 ^ 3) mais pas avec (k <= 10 ^ 3, N <= 10 ^ 9) . J'ai changé n en long long mais il affiche toujours "Terminé en raison du délai d'attente".


    @Erj, quelle est la source des limites? Veuillez fournir un lien. Je les ai basés sur ce que votre code a testé, comment saurais-je le contraire?


    k <= 10 ^ 3, N <= 10 ^ 9 . Les autres trucs sont les mêmes.


    @Erj, j'ai retravaillé le code avec les limites révisées (inversées). J'ai également ajouté un exemple de timing à mesure que vous approchez de ces limites.