1. Solucionario de 3er Cuscontest
PROBLEMA A (HORA DE COMER)
¿Listo para algo de aritmética ingeniosa?, empezaremos por notar que los puntos de
partida de las orugas coinciden con la primera hoja de la rama.
Seguidamente una comida le sigue en cada distancia que es divisible por la distancia
que recorre la oruga y la distancia en donde se encuentra cada hoja, estamos
buscando múltiplos comunes de D (descanso) y H (hoja) por lo tanto tenemos la
formula para calcular el MCM mínimo común múltiplo o (LCM) Least Common Multiple.
def lcm(Descanso, Hoja):
return Descanso / gcd(Descanso, Hoja) * Hoja
el gcd es el MCD (máximo común divisor) que puede ser calculado fácilmente con esta
definición recursiva.
def gcd(D, H):
if (H == 0):
return D
return gcd(H, D % H)
Luego determinamos cuantas veces el mínimo común múltiplo encaja en la longitud de
la rama, agregando 1 por la hoja inicial, de manera que el número total de hojas
comidas es calculado como sigue:
def hojas_comidas(Rama, Descanso , Hoja):
return Rama / lcm(Descanso, Hoja) + 1
También se tiene un enfoque de “fuerza bruta”, dado que la rama más grande es a lo
más 1 millón de unidades de longitud, no tomará mucho en ejecutar una simulación.
El siguiente pseudocódigo muestra como podemos tomar ventaja de las posiciones de
la oruga cada D (distancia y luego descanso) unidades y en cada estación verificamos
si es divisible con la distancia de las hojas, de esa manera verificamos si descanso o
no en una posición donde había una hoja y de esa forma saber si comió o no la hoja
correspondiente a esa posición.
def simulacion_comerhojas(Rama, Descanso, Hoja):
comida = 0
pos = 0
while (pos <= Rama):
if (pos%Hoja == 0):
comida = comida+1
pos = pos+Descanso
2. return comida
PROBLEMA B
PLEGADO DE PAPEL
Creo recordar al Señor Wizard demostrando que no puedes doblar un papel más de 8 veces.
En cualquier caso, este problema es resuelto con un simple algoritmo de fuerza bruta.
Dado que doblar un papel en una dirección es independiente de doblarlo en otra dirección,
tiene sentido considerar ambas direcciones (ancho y largo) independientemente.
También debido a que puedes rotar el papel 90 grados, deberías intentar encajar cada
dimensión del papel en cada dimensión de la caja (ósea primero ancho – largo con ancho-largo
y luego con largo-ancho con ancho largo), si el número de pliegues requeridos para cualquiera
de las direcciones excede 8 entonces esa dirección es invalida, si cualquiera de las direcciones
es invalida escribir -1, caso contrario retornar el mínimo número de los validos, ósea el mínimo
encontrado ya sea teniendo al papel (ancho largo) con la hoja (ancho largo) o al papel rotado
90 grados y aplicado el mismo algoritmo (largo ancho) con la hoja (ancho largo).
3. En la imagen vemos su solución equivalente pero en este caso duplicamos la longitud ya sea
vertical u horizontal de la caja y cuando sea mayor o igual al papel tanto en ancho como en
largo (ósea ya contiene a la hoja) ya hemos encontrado cuantos plegados necesitábamos, esto
se hace tanto (ancho-ancho largo-largo o ancho-largo con largo-ancho) y luego elegir con cual
de los dos hicimos menos pliegues, en el ejemplo vemos que con la primera haríamos menos
pliegues es decir 2.
4. H , V
P r o fu n d id a d 0
H /2 , V H , V /2
P r o fu n d id a d 1
H /4 , V H /2 , V /2 H , V /4
P r o fu n d id a d 2
.. .
...
..
....
...
...
Se puede realizar también un algoritmo de búsqueda completa, en este caso una búsqueda
recursiva ya que podemos doblar a lo más 8 veces, nuestra búsqueda terminaría cuando
alcance una profundidad de 8 (complejidad 2^8 = 255 combinaciones), es decir en cada
recorrido que se haga, elegiremos o bien doblar el ancho o el largo y verificaremos en cada
nodo(el circulo amarillo) si ya se tiene la situación en la que el ancho y el largo ya es menor o
igual que la caja contenedora si es así retornar su profundidad actual caso contrario seguir
buscando hasta a lo más una profundidad de 8, si en la profundidad 8 seguimos sin conseguir
que tanto ancho como largo sea menor que las dimensiones de la caja entonces retornamos
un valor grande constante, para que al final comparemos y si por todos los caminos nos
devuelve ese valor grande significa que por ninguna combinación (ancho-largo o largo-ancho
de plegados) con una profundidad hasta de 8 se pudo conseguir que las dimensiones del papel
sean menores o iguales a las dimensiones de la caja entonces imprimimos -1.
Se adjunta también un caso curioso en el cual la recursión se puede reducir a una tabla
(matriz) ya que hay nodos que contendrán valores similares (flecha roja) entonces ya no será
necesario recorrer cada nodo sino una matriz de 8x8, la solución se deja a la imaginación del
concursante (eso es conocido como DP, programación dinámica y un ejemplo común de esto
es el LCS (longest common subsequence) o subsecuencia común más larga que usualmente lo
enseñan de frente con tabla sin explicarlo que todo empieza con la recursión, al menos así fue
mi historia xD!).
5. PROBLEMA C
ESPIRAL
Dado que este problema no tiene dificultades para ser entendido sino mas bien la dificultad
esta en simular el recorrido, pasaremos a explicar la simulación sin mas.
Este problema puede ser resuelto de varias formas, en ésta ocasión la resolveremos con un
modulo recursivo similar al de recorrer un laberinto.
El modulo para hacer el recorrido en la matriz de dimensiones A[n][n], donde n es el tamaño
de la matriz, es el siguiente:
void backtrack(int x,int y,int dir) {
if((0<=x)&&(x<n)&&(0<=y)&&(y<n)) {
if(A[x][y]==-1) {
A[x][y]=cont++;
if(dir==0) {backtrack(x,y+1,dir); dir++;}
if(dir==1) {backtrack(x+1,y,dir); dir++;}
if(dir==2) {backtrack(x,y-1,dir); dir++;}
if(dir==3) {backtrack(x-1,y,dir); dir++;}
backtrack(x,y+1,dir%4);
}
}
}
Podemos observar que este modulo es similar a la de recorrer un laberinto con la diferencia
que este modulo tiene un parámetro de mas, que es dir, cuya función es de dar la dirección
respectiva para el recorrido de nuestra espiral, al inicio de este modulo preguntamos si los
valores x, y no están saliendo de los parámetros de la matriz A, si fuese así el modulo retorna a
la llamada anterior para cambiar de dirección, dado que la matriz inicialmente esta llena de -1
para llevar el control de “ si todavía no hemos visitado ese casillero “ , es decir si A[x][y] es
igual a -1 entonces ese casillero no se visito, y podemos colocar el contador en dicho casillero
cont++ , luego de esto cada dir nos permitirá saber hacia donde debemos movernos, dir++ se
utiliza para que una vez que hayamos llegado a un extremo de la matriz cambie de dirección y
la ultima llamada recursiva backtrack(x,y+1,dir%4) esta modulo 4 para regresar a la primera
dirección y pueda repetirse el proceso descrito.