Supuestamente iba a terminar de escribir este tutorial la semana pasada, pero la verdad se me acumularon algunas cosas y tuve que postergarlo hasta ahora.
CPRMT:
El problema pide que imprimamos en orden lexicográfico las letras que hay en común entre ambos strings. Una solución sencilla puede ser la siguiente:
- Buscamos la letra 'a' en el primer string y vemos cuantas ocurrencias tiene (llamémosle Aa: ocurrencias de la letra 'a' en el string A) y luego vemos cuantas ocurrencias tiene en el segundo string (Ba).
- Ahora tenemos que imprimir cierta cantidad de aes, pero ¿cuántas aes tenemos que imprimir si tenemos 2 cantidades? Pues la respuesta es el mínimo de ellas, porque el resultado no puede tener mas aes que cualquiera de los strings, si no, la respuesta no sería un substring de los strings originales. Ahora, ¿por qué no imprimimos menos letras que el min(Aa, Ba)? Porque el resultado tiene que tener la máxima longitud posible y para ello tenemos que aprovechar todas las aes que podamos.
- Imprimimos min(Aa, Ba) aes.
- De manera análoga procedemos con las letras desde 'b' hasta la 'z'.
OFFSIDE:
La solución del problema está dicha por el mismo problema:
An attacking player is offside if he is nearer to his opponent's goal line than the second last opponentEntonces, lo único que tenemos que hacer es buscar el jugador del primer equipo que está más cerca de la línea de gol y comparar si está más cerca que el penúltimo jugador del segundo equipo.
Nota importante: la frase "penúltimo jugador del segundo equipo" puede resultar un poco confusa. Si ordenamos las posiciones del segundo equipo en orden no decreciente obteniendo un arreglo B[0..n), B[1] representa la posición del penúltimo jugador y no B[n - 2] como podrían pensar otros, ya que para el segundo equipo entre más cerca de la línea de goal se esté, significa que está más atrás con respecto a los demás.
SAMER08F:
Éste fué el tercer problema más resuelto de la competencia. Podemos subdividir el problema de la siguiente manera: si tenemos un cuadrado de N x N, entonces calculemos el número de cuadrados diferentes de tamaño 1 x 1, 2 x 2, 3 x 3, ..., N x N. Entonces, procedamos a calcular el número de cuadrados diferentes para cada tamaño.
- Para cuadrados de tamaño 1 x 1, tenemos que hay N x N cuadrados diferentes. Cada celda del cuadrado de tamaño N x N mide 1 x 1, y hay N x N celdas.
- Para cuadrados de tamaño 2 x 2, tenemos que hay (N - 1) x (N - 1) cuadrados diferentes. No podemos colocar un cuadrado de tamaño 2 x 2 en la última columna del cuadrado, ya que se excedería del tamaño del cuadrado. Igualmente no podemos colocar un cuadrado de tamaño 2 x 2 en la última fila. Entonces, para cada fila (que son N - 1, ya que la última no es válida) tenemos que podemos colocar cuadrados de tamaño 2 x 2 en todas las columnas, excepto la última.
- Para cuadrados de tamaño 3 x 3, tenemos que hay (N - 2) x (N - 2) cuadrados diferentes. Antes no podíamos colocar un cuadrado de tamaño 2 x 2 en la última fila, ahora como es de tamaño 3 x 3, no podemos colocarlo en la penúltima fila. Igualmente, no podemos colocar cuadrados de tamaño 3 x 3 en la penúltima columna.
- Generalizando, para un cuadrado de tamaño i x i, tenemos que hay (N - i + 1) x (N - i + 1) cuadrados diferentes. Por ejemplo para cuadrados de tamaño 2 x 2, tenemos que hay (N - 2 + 1) x (N - 2 + 1) = (N - 1) x (N - 1) cuadrados diferentes, lo cual encaja con nuestro análisis.
Esta solución es de orden O(N), ya que para cada 1 <= i <= N, hay que hacer un pequeño cálculo (de orden constante). Ahora, ¿puede hacerlo mejor, tal vez en orden O(1)?
BYTESM2:
Un ejercicio sencillo que puede ser resuelto usando Programación Dinámica. Algunos indicativos de que el problema puede ser resuelto usando programación dinámica son: un problema se puede expresar en subproblemas que son del mismo tipo del problema; el subproblema no depende del problema padre, y hay casos que se repiten. Vamos a analizar esos indicativos uno por uno:
- El problema se puede expresar en subproblemas que son del mismo tipo del problema: supongamos que estamos en un punto de la primera fila y nos movemos a la segunda fila. Al movernos a otra fila no podemos regresar a la anterior, por lo tanto lo que era antes la segunda fila se vuelve técnicamente en nuestra nueva primera fila y tenemos el mismo problema que el anterior, encontrar el máximo desde esa primera fila hasta el final y sumarle el beneficio de la vieja primera fila.
- El subproblema no depende del problema padre: cuando llegamos a una posición determinada mediante una secuencia de movimientos, no nos importa como llegamos allí, ní lo que hay antes de nosotros, pues el subproblema tiene que calcular el mejor resultado desde su posición en adelante (gracias al punto anterior).
- Hay casos repetidos: digamos que AB = moverse hacia abajo, AD = moverse hacia abajo y después 1 paso a la derecha y AI = moverse hacia abajo y después 1 paso a la izquierda. Si estamos en la posición (1, 3) (1era fila y 3era columna) y tomamos la secuencia de pasos AB-AB llegaremos a la posición (3, 3). También podemos llegar a esa misma posición desde la posición (1, 3) usando las secuencias de paso AI-AD y AD-AI. Entonces estamos haciendo el cálculo desde la posición (3, 3) al menos 3 veces, en donde todos los resultados serán iguales, porque las celdas de la matriz se mantienen constantes.
# n = número de filas # m = número de columnas function dp(pos: posicion, tab: tablero, n, m: enteros) # dp = dynamic programming si (pos.fila > n) # n es el número de filas retornar 0 # Ya terminamos si (la posicion ha sido calculada anteriormente) retornar el valor correspondiente a esa posicion sino # Procedemos a calcular Calcular cuales de los movimientos (AI, AB, AD) son válidos De los movimientos válidos tomar el que provea mayor beneficio usando la función dp desde la nueva posición indicada por el movimiento retornar el mayor beneficio calculado en el paso anterior + el valor del tablero en la posición dada. Para la función principal tenemos que calcular dp en todas las columnas de la primera fila e imprimir el mayor de todos los valores obtenidos
CANDN:
Éste fué el problema más difícil de la competencia. Hay 3 puntos de interés para el problema: el bar, la casa de Charly y la casa de Nito. Tenemos que obtener la distancia mínima que hay entre el bar y la casa de Charly, y la distancia mínima que hay entre el bar y la casa de Nito, pues ellos no están dispuestos a caminar más de esa distancia. Para obtener esa mínima distancia, podemos usar el Algoritmo de Dijkstra. Ahora, procedemos a resolver el problema de la siguiente manera:
Para cada nodo del grafo: Si la distancia que Charly y Nito han recorrido desde el bar hasta dicho nodo + la distancia que le falta a cada uno por correr sigue siendo aceptable para cada uno (recuerde que ellos están dispuestos a recorrer la distancia mínima del bar a su casa) "tomamos en cuenta" ese nodo. De todos los nodos que "tomamos en cuenta" tomamos aquel donde Charly y Nito han recorrido la mayor distancia juntos.
Al ver la información anterior, nos damos cuenta que aún nos faltan datos adicionales, que son: la distancia da la casa de Nito a cada uno de los nodos y la distancia de la casa de Charly a cada uno de los nodos. Para cada nodo tenemos que saber cuanto le falta a Charly y a Nito por recorrer para llegar a sus respectivos hogares desde ese punto (podemos calcular las distancias desde la casa de cada uno a todos los nodos, y dado que el grafo es no dirigido, podemos decir que la distancia de la casa de X a un nodo Y es igual a la distancia del nodo Y a la casa de X). En resumen, tenemos que hacer uso del algoritmo de Dijkstra sólo 3 veces: para calcular la distancia del bar a todos los nodos, para calcular la distancia de la casa de Charly a cada uno de los nodos y para calcular la distancia de la casa de Nito a cada uno de los nodos. Y por último, usar el procedimiento dicho anteriormente, para resolver el problema.
Con esto termina el tutorial del primer calentamiento para el maratón local de la USB. Espero les haya gustado :-).
Saludos