2010-07-24 10 views
13

Compré un DVD en blanco para grabar mi programa de televisión favorito. Vino con pegatinas de 20 dígitos. 2 de cada uno de '0' - '9'.
Pensé que sería una buena idea etiquetar numéricamente mi nueva colección de DVD. Grabé la pegatina '1' en mi primer DVD grabado y puse las 19 pegatinas sobrantes en un cajón.
Al día siguiente compré otro DVD en blanco (recibí 20 pegatinas nuevas con él) y después de grabar el programa lo etiqueté como '2'.
Y luego comencé a preguntarme: ¿cuándo se acabarán las pegatinas y ya no podré etiquetar un DVD?
Algunas líneas de Python, ¿no?Puzzle que desafía el enfoque de la fuerza bruta?

¿Puede proporcionar un código que resuelva este problema con un tiempo de ejecución razonable?

Editar: La fuerza bruta será simplemente tomar demasiado tiempo a correr. Por favor, mejore su algoritmo para que su código le devuelva la respuesta correcta en, digamos, un minuto?

Crédito adicional: ¿Qué pasa si los DVD vienen con 3 calcomanías de cada dígito?

+7

¿Es esta tarea? – Oded

+0

para que siempre obtenga +20 pegatinas con cada DVD en blanco? – knittl

+2

Suponiendo que desea numerar secuencialmente, podrá contar hasta 10 DVD con 2 o 3 calcomanías por dígito (spd), 11 con 4 spd, 20 con 12 calcomanías por dígito, 30 con 13, y así sucesivamente. La razón principal es que te quedarás sin 1 bastante temprano. –

Respuesta

2

Solución completamente nueva. 6 billones de veces más rápido que el primero.

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

real 0m0.777s 
user 0m0.772s 
sys 0m0.004s 

código:

cache = {} 
def how_many_used(n): 
    if n in cache: 
     return cache[n] 
    result = 0 
    if int(n) >= 10: 
     if n[0] == '1': 
      result += int(n[1:]) + 1 
     result += how_many_used(str(int(n[1:]))) 
     result += how_many_used(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 how_many_have(i, stickers): 
    return int(i) * stickers 

def end_state(i, stickers): 
    if i == '': 
     return 0 
    return how_many_have(i, stickers) - how_many_used(i) 

cache2 = {} 
def lowest_state(i, stickers): 
    if stickers <= 0: 
     return end_state(i, stickers) 
    if i in ('', '0'): 
     return 0 
    if (i, stickers) in cache2: 
     return cache2[(i, stickers)] 

    lowest_candidats = [] 

    tail9 = '9' * (len(i)-1) 
    if i[0] == '1': 
     tail = str(int('0'+i[1:])) 
     lowest_candidats.append(end_state(str(10**(len(i) - 1)), stickers)) 
     lowest_candidats.append(lowest_state(tail, stickers - 1) + end_state(str(10**(len(i) - 1)), stickers)) 
    else: 
     tail = str(int(i[0])-1) + tail9 
     series = end_state(tail9, stickers) 
     if series < 0: 
      lowest_candidats.append(lowest_state(str(int('0'+i[1:])), stickers) + end_state(i[0] + '0'*(len(i)-1), stickers)) 
     lowest_candidats.append(lowest_state(tail, stickers)) 
    result = min(lowest_candidats) 
    cache2[(i, stickers)] = result 
    return result 

def solve(stickers): 
    i=1 
    while lowest_state(str(i), stickers) >= 0: 
     i *= 2 

    top = i 
    bottom = 0 
    center = 0 

    while top - bottom > 1: 
     center = (top + bottom)/2 
     if lowest_state(str(center), stickers) >= 0: 
      bottom = center 
     else: 
      top = center 

    if lowest_state(str(top), stickers) >= 0: 
     return top 
    else: 
     return bottom 

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

for i in xrange(10): 
    print "%d: %d" % (i, solve(i)) 
+0

¿Podría agregar algunas palabras sobre el algoritmo? ¿Qué hace "lowest_state"? También estoy bastante seguro de que hay una forma de calcular how_many_used directamente, sin recursión, pero el código ya es increíblemente rápido ... –

+0

lowest_state devuelve el suministro más bajo de '1' stickers cuando compramos DVDs 'i' con pegatinas 'stickers' en cada uno. De hecho, este número es siempre 0 o negativo (porque el suministro inicial es 0, por lo que el suministro más bajo no puede ser mayor) –

2

aquí es un script rápido y sucio pitón:

#!/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("No stickers left after " + str(disc) + " discs.") 
print("Remaining stickers: " + str(stickers)) 

no sé si se da el resultado correcto sin embargo. si encuentra errores lógicos, por favor comentar

resultado con la salida de depuración:

Bought disc 199991. Labels: 
Remaining stickers: {0: 111102, 1: 0, 2: 99992, 3: 99992, 4: 99992, 5: 99997, 6: 99992, 7: 99992, 8: 99992, 9: 100024} 
+5

MPAA enviará a sus matones a su casa antes de comprar incluso la mitad de ese número. :-) –

+0

Parece que funciona correctamente, pero solo está agregando 1 calcomanía de cada 0-9 en cada ronda. Agregar 2 respondería la pregunta original, pero es probable que no se detenga pronto ... – Jari

+0

@ jari, hm, sí. tienes razón, te lo perdiste ... – knittl

1

Los resultados para cualquier base N y número de etiquetas por dígito por DVD "S" son:

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 | 
----+--------+ 

No puedo ver ningún patrón.


Alternativamente, si la etiqueta se inicia desde 0 en lugar 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 | 
----+---------+ 

de asumen que es el “1” etiqueta acabando primero vamos - que es de hecho el caso para la mayoría de otra información computada

Supongamos que estamos en la base N y que habrá S nuevas pegatinas por dígito por DVD.

En el DVD #X, habrá etiquetas totalmente "0" X × S, usadas o no.

El número de pegatinas "1" utilizadas es solo el número de "1" en los dígitos del 1 al X en la expansión de la base N.

Por lo tanto, solo tenemos que encontrar el punto de cruce de X × S y el conteo total de dígitos "1".

no parece ser un cerrado de todas estas secuencias, por lo que un bucle proporcional X iteraciones es necesario. Los dígitos se pueden extraer en tiempo log X, por lo que, en principio, el algoritmo puede finalizar en tiempo O (X log X).

Esto no es mejor que el otro algoritmo, pero al menos se pueden eliminar muchos cálculos. Un código de ejemplo 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; 
} 
+0

4^S y 27^S y .... tal vez es (S^S)^S? – knittl

+0

@knittl: aunque no funcionará para N> 3. – kennytm

6

Aquí es prueba de que existe una solución:

Asumiendo que nunca llega a 21 dígitos, que comenzará a perder una pegatina con todos los DVD que compre y la etiqueta ((+20) + (-21))
No importa cuántas pegatinas haya acumulado hasta este momento. A partir de ahora, todo es cuesta abajo para su escondite de pegatinas y eventualmente se acabará.

+2

A los que votaron por esta publicación: Esto es por el propio OP. OP: Por favor, ponga esta información en su publicación original. –

+5

@silky: Responder su propia pregunta es útil y alentador, ya que se agrega a la base de conocimiento de la comunidad. – Daenyth

+0

@Daenyth: * suspiro *. Desearía no tener que explicar esto, pero: 1) No estoy en desacuerdo, 2) El op new esta información en el momento de publicar la pregunta. –

7

Esta es la solución anterior, completamente nueva solución de 6 bajillion times is on the bottom.

Solución:

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 

Código:

OPTIMIZE_1 = True # we assum that '1' will run out first (It's easy to prove anyway) 

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

def how_many_have(dight, n, stickers): 
    return stickers * n 

cache = {} 
def how_many_used(dight, n): 
    if (dight, n) in cache: 
     return cache[(dight,n)] 
    result = 0 
    if dight == "0": 
     if OPTIMIZE_1: 
      return 0 
     else: 
      assert(False) 
      #TODO 
    else: 
     if int(n) >= 10: 
      if n[0] == dight: 
       result += int(n[1:]) + 1 
      result += how_many_used(dight, str(int(n[1:]))) 
      result += how_many_used(dight, str(int(str(int(n[0])-1) + "9"*(len(n) - 1)))) 
     else: 
      result += 1 if n >= dight else 0 
    if n.endswith("9" * (len(n)-4)): # '4' constant was pick out based on preformence tests 
     cache[(dight, n)] = result 
    return result 

def best_jump(i, stickers_left): 
    no_of_dights = len(str(i)) 
    return max(1, min(
     stickers_left/no_of_dights, 
     10 ** no_of_dights - i - 1, 
    )) 

def solve(stickers): 
    i = 0 
    stickers_left = 0 
    while stickers_left >= 0: 
     i += best_jump(i, stickers_left) 

     stickers_left = min(map(
      lambda x: how_many_have(x, i, stickers) - how_many_used(str(x), str(i)), 
      NUMBERS 
     )) 
    return i - 1 

for stickers in range(10): 
    print '%d: %d' % (stickers, solve(stickers)) 

Demostrar que '1' se quedará sin primero:

def(number, position): 
    """ when number[position] is const, this function is injection """ 
    if number[position] > "1": 
     return (position, number[:position]+"1"+number[position+1:]) 
    else: 
     return (position, str(int(number[:position])-1)+"1"+number[position+1:]) 
+0

¡Y un hermoso patrón emerge +1! ¡Muy genial! Jugando con eso ahora. ¿Alguien puede explicar el patrón resultante? Aquí está la lógica, como yo lo entiendo: si estás en dvd # I (I es un número de dígito N), y tienes S stickers en tu escondite de sobras, puedes avanzar rápidamente sin consultar los siguientes números de S/N (asumiendo que quedan muchos números de N dígitos).Este es el significado de 'min (stickers_left/no_of_dights, 10 ** (no_of_dights + 1) - i - 1)' –

+0

¡Menos de 1 segundo para ejecutarse en un escritorio muy antiguo (para la versión de 2 adhesivos solicitada)! Menos de 5 segundos para resolver la versión de 3 adhesivos ... –

+0

Me alegra ver que está satisfecho con esta solución. –

1

Aquí hay algunas ideas sobre el límite superior demostrada por @Tal Weiss:

El primer número de 21 dígitos es 10^20, en cuyo punto tendremos a lo sumo20 * 10^20 pegatinas. Cada DVD posterior nos costará al menos 1 etiqueta neta, por lo que definitivamente nos hemos quedado sin 10^20 + 20 * 10^20, que equivale a 21 * 10^20. Esto es por lo tanto un límite superior en la solución. (¡No es un límite superior particularmente apretado por ningún medio! Pero uno que es fácil de establecer).

Generalizar el resultado anterior a la base b:

  • cada DVD viene con 2b pegatinas
  • el primer DVD que nos cuesta 1 pegatina neto es el número b^(2b), momento en el que vamos a tener como máximo 2b . b^(2b) pegatinas
  • así que sin duda agotado por b^(2b) + 2b . [b^(2b)], lo que equivale a (2b + 1)[b^(2b)]

Entonces, por ejemplo, si trabajamos en la base 3, este cálculo da un límite superior de 5103; en la base 4, es 589824.Estos son números que serán mucho más fáciles de resolver con fuerza bruta/matemáticamente.

Cuestiones relacionadas