13 votes

Casse-tête qui défie l'approche de la force brute?

J'ai acheté un DVD vierge pour enregistrer mon émission de télévision préférée. Il est venu avec 20 autocollants numériques. 2 de chaque chiffre de '0' à '9'.
Je pensais que ce serait une bonne idée d'étiqueter numériquement ma nouvelle collection de DVD. J'ai collé l'autocollant '1' sur mon premier DVD enregistré et j'ai mis les 19 autocollants restants dans un tiroir.
Le lendemain, j'ai acheté un autre DVD vierge (recevant 20 nouveaux autocollants avec celui-ci) et après avoir enregistré l'émission, je l'ai étiqueté '2'.
Et puis je me suis demandé : quand les autocollants seront-ils épuisés et que je ne pourrai plus étiqueter un DVD?
Quelques lignes de Python, non?

Pouvez-vous fournir du code qui résout ce problème avec un temps d'exécution raisonnable?

Modifier : La méthode de force brute prendrait simplement trop de temps pour s'exécuter. Veuillez améliorer votre algorithme afin que votre code puisse renvoyer la bonne réponse en, disons, une minute?

Crédit supplémentaire : Et si les DVDs étaient livrés avec 3 autocollants de chaque chiffre?

7voto

Tomasz Wysocki Points 4392

C'est la vieille solution, une solution complètement nouvelle 6 milliards de fois plus rapide est en bas.

Solution:

time { python solution.py; } 
0: 0
1: 199990
2: 1999919999999980
3: 19999199999999919999999970
4: 199991999999999199999999919999999960
5: 1999919999999991999999999199999999919999999950
6: 19999199999999919999999991999999999199999999919999999940
7: 199991999999999199999999919999999991999999999199999999919999999930
8: 1999919999999991999999999199999999919999999991999999999199999999919999999920
9: 19999199999999919999999991999999999199999999919999999991999999999199999999919999999918

real    1m53.493s
user    1m53.183s
sys 0m0.036s

Code:

OPTIMIZE_1 = True # nous supposons que '1' s'épuisera en premier (c'est facile à prouver de toute façon)

if OPTIMIZE_1:
    NUMBERS = [1]
else:
    NUMBERS = range(10)

def combien_ont(chiffre, n, autocollants):
    return autocollants * n

cache = {}
def combien_utilise(chiffre, n):
    if (chiffre, n) in cache:
        return cache[(chiffre,n)]
    result = 0
    if chiffre == "0":
        if OPTIMIZE_1:
            return 0
        else:
            assert(False)
            #TODO
    else:
        if int(n) >= 10:
            if n[0] == chiffre:
                result += int(n[1:]) + 1
            result += combien_utilise(chiffre, str(int(n[1:])))
            result += combien_utilise(chiffre, str(int(str(int(n[0])-1) + "9"*(len(n) - 1))))
        else:
            result += 1 if n >= chiffre else 0
    if n.endswith("9" * (len(n)-4)): # '4' constante a été choisie en fonction des tests de performances
        cache[(chiffre, n)] = result
    return result

def meilleur_saut(i, autocollants_restants):
    nb_chiffres = len(str(i))
    return max(1, min(
        autocollants_restants / nb_chiffres,
        10 ** nb_chiffres - i - 1,
    ))

def resoudre(autocollants):
    i = 0
    autocollants_restants = 0
    while autocollants_restants >= 0:
        i += meilleur_saut(i, autocollants_restants)

        autocollants_restants = min(map(
            lambda x: combien_ont(x, i, autocollants) - combien_utilise(str(x), str(i)),
            NUMBERS
        ))
    return i - 1

for autocollants in range(10):
    print '%d: %d' % (autocollants, resoudre(autocollants))

Prouvez que '1' s'épuisera en premier:

def(nombre, position):
    """ lorsque le nombre[position] est constant, cette fonction est une injection """
    if nombre[position] > "1":
        return (position, nombre[:position]+"1"+nombre[position+1:])
    else:
        return (position, str(int(nombre[:position])-1)+"1"+nombre[position+1:])

6voto

Tal Weiss Points 2763

Voici la preuve qu'une solution existe:

En supposant que vous arriviez un jour à des nombres de 21 chiffres, vous commencerez à perdre un autocollant à chaque DVD que vous achetez et étiquetez ((+20) + (-21)).
Peu importe le nombre d'autocollants que vous avez accumulés jusqu'à ce point. À partir de là, c'est la descente aux enfers pour votre réserve d'autocollants et vous finirez par en manquer.

2voto

knittl Points 64110

Voici un script python rapide et sale:

#!/bin/env python

disc = 0
stickers = {
    0: 0, 1: 0,
    2: 0, 3: 0,
    4: 0, 5: 0,
    6: 0, 7: 0,
    8: 0, 9: 0 }

def buyDisc():
    global disc
    disc += 1
    for k in stickers.keys():
        stickers[k] += 1

def labelDisc():
    lbl = str(disc)
    for c in lbl:
        if(stickers[int(c)] <= 0): return False;
        stickers[int(c)] -= 1;
    return True

while True:
    buyDisc()
    if not labelDisc(): break

print("Plus d'autocollants restants après " + str(disc) + " disques.")
print("Autocollants restants: " + str(stickers))

je ne sais pas si cela produit le résultat correct cependant. si vous trouvez des erreurs logiques, veuillez commenter

résultat avec la sortie de débogage:

Disque acheté 199991. Étiquettes: 
Autocollants restants: {0: 111102, 1: 0, 2: 99992, 3: 99992, 4: 99992, 5: 99997, 6: 99992, 7: 99992, 8: 99992, 9: 100024}

2voto

Tomasz Wysocki Points 4392

Nouvelle solution. 6 bajillions de fois plus rapide que la première.

temps { python clean.py ; }
0: 0
1: 199990
2: 1999919999999980
3: 19999199999999919999999970
4: 199991999999999199999999919999999960
5: 1999919999999991999999999199999999919999999950
6: 19999199999999919999999991999999999199999999919999999940
7: 199991999999999199999999919999999991999999999199999999919999999930
8: 1999919999999991999999999199999999919999999991999999999199999999919999999920
9: 19999199999999919999999991999999999199999999919999999991999999999199999999919999999918

réel    0m0.777s
utilisateur    0m0.772s
système 0m0.004s

code:

cache = {}
def combien_utilisé(n):
    if n in cache:
        return cache[n]
    result = 0
    if int(n) >= 10:
        if n[0] == '1':
            result += int(n[1:]) + 1
        result += combien_utilisé(str(int(n[1:])))
        result += combien_utilisé(str(int(str(int(n[0])-1) + "9"*(len(n) - 1))))
    else:
        result += 1 if n >= '1' else 0
    if n.endswith("9" * (len(n)-0)) or n.endswith("0" * (len(n)-1)):
        cache[n] = result
    return result

def combien_avoir(i, autocollants):
    return int(i) * autocollants

def état_final(i, autocollants):
    if i == '':
        return 0
    return combien_avoir(i, autocollants) - combien_utilisé(i)

cache2 = {}
def état_plus_bas(i, autocollants):
    if autocollants <= 0:
        return état_final(i, autocollants)
    if i in ('', '0'):
        return 0
    if (i, autocollants) in cache2:
        return cache2[(i, autocollants)]

    candidats_plus_bas = []

    tail9 = '9' * (len(i)-1)
    if i[0] == '1':
        tail = str(int('0'+i[1:]))
        candidats_plus_bas.append(état_final(str(10**(len(i) - 1)), autocollants))
        candidats_plus_bas.append(état_plus_bas(tail, autocollants - 1) + état_final(str(10**(len(i) - 1)), autocollants))
    else:
        tail = str(int(i[0])-1) + tail9
        série = état_final(tail9, autocollants)
        if série < 0:
             candidats_plus_bas.append(état_plus_bas(str(int('0'+i[1:])), autocollants) + état_final(i[0] + '0'*(len(i)-1), autocollants))
        candidats_plus_bas.append(état_plus_bas(tail, autocollants))
    résultat =  min(candidats_plus_bas)
    cache2[(i, autocollants)] = résultat
    return résultat

def résoudre(autocollants):
    i=1
    while état_plus_bas(str(i), autocollants) >= 0:
        i *= 2

    top = i
    bottom = 0
    center = 0

    while top - bottom > 1:
        center = (top + bottom) / 2
        if état_plus_bas(str(center), autocollants) >= 0:
            bottom = center
        else:
            top = center

    if état_plus_bas(str(top), autocollants) >= 0:
        return top
    else:
        return bottom

import sys
sys.setrecursionlimit(sys.getrecursionlimit() * 10)

for i in xrange(10):
    print "%d: %d" % (i, résoudre(i))

1voto

KennyTM Points 232647

Les résultats pour toute base N et nombre d'autocollants par chiffre par DVD "S" sont :

N\S ]      1 |        2 |          3 |         4 |    5 |        S?
===================================================================
  2 ]      2 |       14 |         62 |       254 | 1022 |   4^S - 2
----+--------+----------+------------+-----------+------+----------
  3 ]     12 |      363 |       9840 |    265719 |     (27^S - 3)/2
----+--------+----------+------------+-----------+-----------------
  4 ]     28 |     7672 |    1965558 | 503184885 |
----+--------+----------+------------+-----------+
  5 ]    181 |   571865 | 1787099985 |
----+--------+----------+------------+
  6 ]    426 | 19968756 |
----+--------+----------+
  7 ]   3930 | (≥ 2^31) |
----+--------+----------+
  8 ]   8184 |
----+--------+
  9 ] 102780 |
----+--------+
 10 ] 199990 |
----+--------+

Je ne peux pas voir de motifs.


Alternativement, si l'autocollant commence à partir de 0 au lieu de 1,

N\S ]       1 |        2 |          3 |         4 |    5 |          S?
======================================================================
  2 ]       4 |       20 |         84 |       340 | 1364 | (4^S-1)*4/3
----+---------+----------+------------+-----------+------+------------
  3 ]      12 |      363 |       9840 |    265719 |       (27^S - 3)/2
----+---------+----------+------------+-----------+-------------------
  4 ]      84 |     7764 |    1965652 | 503184980 |
----+---------+----------+------------+-----------+
  5 ]     182 |   571875 | 1787100182 |
----+---------+----------+------------+
  6 ]    1728 | 19970496 |
----+---------+----------+
  7 ]    3931 | (≥ 2^31) |
----+---------+----------+
  8 ]   49152 |
----+---------+
  9 ]  102789 |
----+---------+
 10 ] 1600000 |
----+---------+

Supposons que c'est l'autocollant "1" qui se termine en premier - ce qui est en effet le cas pour la plupart des autres informations calculées.

Supposons que nous sommes en base N et qu'il y aura S nouveaux autocollants par chiffre par DVD.

À la DVD n°X, il y aura totalement X×S autocollants "1", utilisés ou non.

Le nombre d'autocollants "1" utilisés est simplement le nombre de "1" dans les chiffres de 1 à X dans l'expansion en base N.

Il suffit donc de trouver le point de croisement entre X×S et le total du nombre de chiffres "1".

Il ne semble pas y avoir de formule fermée pour toutes ces séquences, donc une boucle proportionnelle à X est nécessaire. Les chiffres peuvent être extraits en log X temps, donc en principe l'algorithme peut être terminé en O(X log X) temps.

Ce n'est pas mieux que l'autre algorithme mais au moins beaucoup de calculs peuvent être supprimés. Un exemple de code C :

#include <stdio.h>

static inline int ones_in_digit(int X, int N) {
    int res = 0;
    while (X) {
        if (X % N == 1)
            ++ res;
        X /= N;
    }
    return res;
}

int main() {
    int N, S, X;

    printf("Base N?   ");
    scanf("%d", &N);
    printf("Stickers? ");
    scanf("%d", &S);

    int count_of_1 = 0;
    X = 0;
    do {
        ++ X;
        count_of_1 += S - ones_in_digit(X, N);
        if (X % 10000000 == 0)
            printf("%d -> %d\n", X/10000000, count_of_1);
    } while (count_of_1 >= 0);
    printf("%d\n", X-1);
    return 0;
}

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