ACM (
Association for Computing Machinery) organiza unas competencias de programación llamadas
ICPC (
International Collegiate Programming Contest). En esta dirección hay muchos problemas de las competencias:
http://acm.uva.es/problemset/.
Este es el enunciado del primer problema de la guía: http://acm.uva.es/p/v1/100.html
El fin de semana hice esta solución:
(nota: los lenguajes soportados por las competencias son C y Java, mi solución está escrita en Python por que me resulta más ágil. De todas formas importa más el algoritmo que el lenguaje.)
def odd(n):
return not (n%2 == 0)
def tnu(n=1):
print n
if (n == 1): return
elif (odd(n)): n = 3*n+1
else: n = n/2
tnu(n) # la recursividad por la cola es evitable
>>> tnu(22)
22
11
34
17
52
26
13
40
20
10
5
16
8
4
2
1
La longitud del ciclo para un n dado es la cantidad de valores que la función tnu() develve (incluido el 1 final)
El problema pide leer un archivo con pares i,j y para cada par devolver la mayor longitud de ciclo para todos los enteres enter i y j incluyendo a i y a j.
Solución Exaustiva
Una solución exaustiva podría consistir en calcular para cada par i,j todos las longitudes de ciclo asociadas y devolver la mayor.
Redefino tnu para que en lugar de imprimir los sucesivos valores de n devulva una lista con los valores, el segundo argumento de la función es una lista vacía.
def tnu(n,l):
#print n
l.append(n)
if (n == 1): return l
elif (odd(n)): n = 3*n+1
else: n = n/2
return tnu(n,l) # la recursividad por la cola es evitable
Suponiendo que ya se leyeron los pares del archivo correspondiente y se tienen en una lista llamada pairs:
def solv1(pairs):
for i,j in pairs:
maxc = 0
for n in range(i,j+1):
maxc = max(len(tnu(n,[])),maxc)
print i, j, maxc
>>> pairs = [(1,10),(88,123),(7,1),(55,455),(10000,10003)]
>>> solv1(pairs)
1 10 20
88 123 119
7 1 0
55 455 144
10000 10003 180
>>> pairs = [(1,10),(100,200),(201,210),(900,1000)]
>>> solv1(pairs)
1 10 20
100 200 125
201 210 89
900 1000 174
Bien, esta solución ANDA, pero no es óptima. De hecho probablemente sea la solución menos eficiente :-) la de la fuerza bruta.
Solución Dinámica
La idea es almacenar los resultados ya calculados para no tener que hacerlos una y otra vez. Si se pide la máxima longitud de ciclo (mlc) enter 100 y 200 y obtengo 125 y luego se pide la máxima longitud de ciclo enter 1 y 300, voy a calcular la mlc para el par 1,100, luego lo haré para 200,300 y luego compararé los resultados con 125 quedándome con el mayor de los 3.
Pero.. que pasa si ya he calculado la mlc para los valores entre 2 y 99?
def solv1din(pairs,dic):
for i,j in pairs:
maxc = f_maxc(i,j,dic)
print i, j, maxc
dic[i,j]=maxc
def f_maxc(i,j,dic):
wide = 0
for a,b in dic.keys():
if a == i and b == j: return dic[a,b]
if (i<=a and b<=j and b-a > wide):
f,g = a,b
wide = b-a
if (wide == 0): #ningún rango del diccionario estaba dentro de i,j
maxc = 0
for n in range(i,j+1):
maxc = max(len(tnu(n,[])),maxc)
return maxc
else:
return max(f_maxc(i,f,dic),dic[f,g],f_maxc(g,j,dic))
>>> solv1din(pairs,{})
1 10 20
100 200 125
201 210 89
900 1000 174
Una versión no recursiva de tnu podría ser algo como esto:
def tnu(n):
l = []
while(n != 1):
l.append(n)
if (odd(n)): n = 3*n+1
else: n = n/2
l.append(1)
return l
Download
- Una implementación completa que resuelve el problema: 3n+1.py
- Un archivo de entrada de ejemplo: input100.txt
Ejemplo de uso
juanjo@sarge:~/problemas$ ./3n+1.py input100.txt
1 10 20
100 200 125
201 210 89
900 1000 174
88 123 119
7 1 0
55 455 144
10000 10003 180