7 votes

Algorithme pour faire des nombres à partir de bâtons d'allumettes

J'ai fait un programme pour résoudre ce problème de l'ACM.

Les bâtonnets d'allumettes sont des outils idéaux pour représenter les nombres. Une façon courante de représenter les dix chiffres décimaux avec des allumettes est la suivante :

C'est identique à la façon dont les chiffres sont affichés sur un réveil ordinaire. Avec un nombre donné d'allumettes, vous pouvez générer un large éventail de chiffres. Nous nous demandons quels sont les nombres les plus petits et les plus grands qui peuvent être créés en utilisant toutes vos allumettes.

Entrée

Sur la première ligne un nombre positif : le nombre de testcases, au maximum 100. Après cela, par cas de test :

Une ligne avec un nombre entier n (2 n 100) : le nombre d'allumettes que vous avez. Sortie

Par cas d'essai :

Une ligne avec le plus petit et le plus grand nombre que vous pouvez créer, séparés par un espace. Les deux nombres doivent être positifs et ne pas contenir de zéros en tête. Exemple de saisie

4 3 6 7 15 Exemple de sortie

7 7 6 111 8 711 108 7111111

Le problème est qu'il est beaucoup trop lent de le résoudre pour 100 allumettes. L'arbre de recherche est trop grand pour le forcer brutalement.

Voici les résultats des 10 premiers :

2 : 1 1

3 : 7 7

4 : 4 11

5 : 2 71

6 : 6 111

7 : 8 711

8 : 10 1111

9 : 18 7111

10 : 22 11111

Le schéma pour les maximums est facile mais je ne vois pas de raccourci pour les minimums. Quelqu'un peut-il suggérer une meilleure façon de résoudre ce problème ? Voici le code que j'ai utilisé :

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

    #define MAX 20 //should be 100

    //match[i] contains number of matches needed to form i
    int match[] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};
    string mi[MAX+1], ma[MAX+1];
    char curr[MAX+1] = "";

    //compare numbers saved as strings
    int mycmp(string s1, string s2)
    {
        int n = (int)s1.length();
        int m = (int)s2.length();
        if (n != m)
            return n - m;
        else
            return s1.compare(s2);
    }

    //i is the current digit, used are the number of matchsticks so far
    void fill(int i, int used)
    {
        //check for smaller and bigger values
        if (mycmp(curr, mi[used]) < 0) mi[used] = curr;
        if (mycmp(curr, ma[used]) > 0) ma[used] = curr;

        //recurse further, don't start numbers with a zero
        for (int a = i ? '0' : '1'; a <= '9'; a++) {
            int next = used + match[a-'0'];
            if (next <= MAX) {
                curr[i] = a;
                curr[i+1] = '\0';
                fill(i + 1, next);
            }
        }
    }

    int main()
    {
        //initialise 
        for (int i = 0; i <= MAX; i++) {
            mi[i] = string(MAX, '9');
            ma[i] = "0";
        }

        //precalculate the values
        fill(0, 0);

        int n;
        cin >> n;

        //print those that were asked
        while (n--) {
            int num;
            cin >> num;
            cout << mi[num] << " " << ma[num] << endl;
        }

        return 0;
    }

EDIT : J'ai fini par utiliser la solution de la programmation dynamique. J'ai déjà essayé avec la programmation dynamique, mais je m'amusais avec un tableau d'états à deux dimensions. Les solutions proposées ici sont bien meilleures. Merci !

4voto

Clément Points 1439

Vous pouvez utiliser une solution de programmation dynamique.

Supposons que vous ayez n allumettes, et que vous sachiez comment résoudre le problème (nombre minimum) pour tous les ensembles de n-k les matches, où k prend toutes les valeurs correspondant au nombre de correspondances que chaque numéro utilise (2 pour 1, 5 pour 3, etc.)

La solution est ensuite dérivée de manière récursive. En supposant que vous terminiez votre nombre par un 1 (dans la position la moins significative), la meilleure solution est obtenue en écrivant (best solution with n-2 matches) 1 . En supposant que vous le terminez par un deux, la meilleure solution est (best solution with n-5 matches) 2 . Et ainsi de suite ; au final, vous pourrez comparer ces dix numéros et choisir le meilleur.

Alors maintenant, tout ce que vous avez à faire, c'est de trouver la meilleure solution pour tout le monde. n plus petit que votre entrée, de manière récursive.

EDIT : Si vous mettez en œuvre cet algorithme de manière directe, vous obtiendrez une complexité exponentielle. L'astuce ici est de remarquer que si votre fonction principale MinimumGivenNMatches ne prend qu'un seul paramètre, n . Vous finirez donc par l'appeler avec les mêmes valeurs un très grand nombre de fois.

Pour rendre la complexité linéaire, il suffit de mémoriser (c'est-à-dire se souvenir) de la solution pour chaque n en utilisant un tableau auxiliaire.

2voto

Benoît Points 10901

Afin de trouver le résultat :

  • trouver d'abord le nombre minimal de chiffres pour le plus petit nombre
  • puis procéder du chiffre le plus significatif au chiffre le moins significatif.

Chaque chiffre doit être choisi de manière à ce qu'il existe une solution pour les chiffres restants. Chaque chiffre nécessite entre 2 et 7 correspondances. Vous devez donc choisir le plus petit Nième chiffre "supérieur" qui laisse le nombre de correspondances restantes entre 2*(N-1) et 7*(N-1).

N'oubliez pas que le 0 doit être exclu de la recherche du chiffre le plus significatif du résultat.

En passant, l'une des raisons pour lesquelles cet algorithme fonctionne est le fait qu'il existe au moins un chiffre correspondant à chaque valeur (de correspondance) entre 2 et 7.

EDIT : exemple pour 10 correspondances 10 correspondances --> 2 chiffres
Nombre acceptable de correspondances pour le chiffre supérieur = entre 3 et 7.
Le plus petit chiffre qui nécessite entre 3 et 7 correspondances -> 2 (qui nécessite 5 correspondances), le 0 étant exclu.
Premier chiffre choisi = 2

5 matchs restants -->
Nombre acceptable de correspondances pour le deuxième (et dans ce cas dernier) chiffre = exactement 5
Le plus petit chiffre qui nécessite 5 correspondances -> 2
Deuxième chiffre choisi = 2

Résultat = 22.

EDIT code pour ce problème

#include <iostream>
#include <vector>

std::vector<int> nbMatchesForDigit;

long long minNumberForMatches(unsigned int nbMatches)
{
    int nbMaxMatchesForOneDigit = 7;
    int nbMinMatchesForOneDigit = 2;
    int remainingMatches = nbMatches;
    int nbDigits = 1 + nbMatches / nbMaxMatchesForOneDigit; 
    long long result = 0;
    for (int idDigit = 0 ; idDigit < nbDigits ; ++idDigit )
    {
        int minMatchesToUse = std::max(nbMinMatchesForOneDigit, remainingMatches - nbMaxMatchesForOneDigit * (nbDigits - 1 - idDigit));
        int maxMatchesToUse = std::min(nbMaxMatchesForOneDigit, remainingMatches - nbMinMatchesForOneDigit * (nbDigits - 1 - idDigit));
        for (int digit = idDigit > 0 ? 0 : 1 ; digit <= 9 ; ++digit )
        {
            if( nbMatchesForDigit[digit] >= minMatchesToUse && 
                nbMatchesForDigit[digit] <= maxMatchesToUse )
            {
                result = result * 10 + digit;
                remainingMatches -= nbMatchesForDigit[digit];
                break;
            }
        }
    }
    return result;
}

int main()
{
    nbMatchesForDigit.push_back(6);
    nbMatchesForDigit.push_back(2);
    nbMatchesForDigit.push_back(5);
    nbMatchesForDigit.push_back(5);
    nbMatchesForDigit.push_back(4);
    nbMatchesForDigit.push_back(5);
    nbMatchesForDigit.push_back(6);
    nbMatchesForDigit.push_back(3);
    nbMatchesForDigit.push_back(7);
    nbMatchesForDigit.push_back(6);

    for( int i = 2 ; i <= 100 ; ++i )
    {
        std::cout << i << " " << minNumberForMatches(i) << std::endl;
    }
}

2voto

tskuzzy Points 19279

Utilisez programmation dynamique au lieu de la récursion. Il est nettement plus rapide de stocker les valeurs calculées et de les réutiliser. En fait, cela transforme un temps d'exécution exponentiel en un temps polynomial.

L'idée de base est d'avoir un tableau min qui garde la trace du nombre minimum qui peut être fait en utilisant exactement n allumettes. Donc

min[0] = ""
min[1] = infinity
min[2] = "1"
min[3] = min("1+"min[3-2],"7")
min[4] = min("1"+min[4-2],"7"+min[4-3])
etc

1voto

Ben Voigt Points 151460

Pour les minima, notez qu'étant donné qu'aucun zéros de tête n'est autorisé, vous voulez minimiser le nombre de chiffres. Le nombre minimal de chiffres est ceil(n/7) .

Il est alors assez facile de calculer le nombre minimum d'allumettes qui DOIVENT être utilisées dans le chiffre de tête, à partir duquel vous obtenez la plus petite valeur possible du chiffre de tête.

0voto

Sajal Points 483

Je suis capable de résoudre le problème avec O(d) où d est le nombre de chiffres. L'idée est la suivante : on calcule d'abord le nombre minimal de chiffres pour le nombre minimal souhaité. Ce nombre peut être calculé comme suit int nofDigits = n/7+ ((0 == n%7) ? 0 : 1) , donde n est le nombre d'allumettes. Créez maintenant un tableau de nofDigits . Maintenant, nous commençons à remplir le maximum d'allumettes possibles (7) à partir du chiffre le moins significatif jusqu'à un chiffre avant le chiffre le plus significatif (MSD) et à la fin, nous attribuons toutes les allumettes restantes au chiffre le plus significatif (MSD). Il y a maintenant 3 possibilités d'amélioration en fonction du nombre d'allumettes pour le MSD :

1. Si le nombre d'allumettes pour MSD est de 1, nous pouvons le rendre égal à 2 en empruntant une allumette à son chiffre adjacent. Le nombre d'allumettes pour le chiffre adjacent sera donc de 6, ce qui équivaut à 0.

2d si le nombre d'allumettes pour MSD est de 4, alors comme dans le cas précédent nous pouvons augmenter le nombre d'allumettes pour MSD à 5, ce qui est équivalent à 2.

3èmement si le nombre d'allumettes pour MSD est 3, alors nous devons voir si le nombre total de chiffres est plus de 2 alors nous pouvons décrémenter 1 de deux chiffres adjacents de MSD ou bien nous décrémenterons un chiffre adjacent deux fois et augmenterons le nombre d'allumettes pour MSD par 2.

A la fin, en parcourant le tableau et en remplaçant le nombre d'allumettes par le chiffre correspondant.

Le programme complet :

void minNumWithNMatches_no_dp (int n) {
    if (n < 2) return ;

    int digits[] = {-1, -1, 1, 7, 4, 2, 0, 8};

    int nofDigits = n/7+ ((0 == n%7) ? 0 : 1);

    int *matchesArr = new int [nofDigits];

    int tmp = n;
    for (int i = 0; i < nofDigits; ++i) {
        matchesArr[i] = tmp/7 ? 7 : tmp%7;
        tmp -= matchesArr[i];
    }

    if (nofDigits > 1)
    {
        switch (matchesArr[nofDigits - 1]) {
        case 1:
        case 4:
            {
                --matchesArr[nofDigits - 2];
                ++matchesArr[nofDigits - 1];
                break;
            }
        case 3:
            {
                if (nofDigits > 2) {
                    --matchesArr[nofDigits - 2];
                    --matchesArr[nofDigits - 3];
                } else {
                    matchesArr[nofDigits - 2] -= 2;
                }
                matchesArr[nofDigits - 1] += 2;
                break;
            }
        case 2:
        case 5:
        case 6:
        case 7:
        default:
            {
                ;
            }
        }
    }
    for (int i = nofDigits - 1; i >= 0; --i) {
        int digit = digits[matchesArr[i]];
        if ((i == nofDigits - 1) && (digit == 0)) digit = 6;
        std::cout<< digit;
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X