Otro de mapas. Inicialmente parecia más facil pero me ha gustado el reto.
Primero probé a hacer un dijkstra buscando el camino mas corto y luego repetir lo mismo por cada posible cheat. La forma en la que lo implementé lo hacia con la misma cola usando "(cost, cheated_at, (y,x))" (cheated_at=None para cuando aun no habia hecho cheat hasta ese punto). Me funcionaba bien para el ejemplo pero para el input era demasiado lento.
Sabia que tardaba porque, cuando por ejemplo hacia un cheat de A a B, el calculo de B a END lo estaba repitiendo siempre. Además, si de A a B iba hacia atrás entonces otro x2.
La realidad es que al repensarlo la solución era mucho mas facil:
1 - Buscar el camino de START a END sin cheats de nada, y guardarme el camino de puntos + el coste para llegar a cada punto.
2 - Por cada punto del camino legal, probar a hacer trampas desde ahi: Al saltar 2 casillas, el punto nuevo deberia estar en el camino original. Si el coste original en ese punto es mas grande que el coste actual + 2, entonces es un cheat bueno.
De esta forma la parte de hacer cheats es O(n) y super rapido
Parte 2:
La mayoria del tiempo la perdí intentando cambiar la parte de "cheating" de la parte1 e intentar enchufarle otro dijkstra para navegar por los "#" con distancia maxima 20. No me di cuenta que en esos "20 pasos" se podia pasar por multiples ".", osea que lo estaba haciendo mal ...
Al final lo que hice fue, iterar todos los puntos del camino legal y, por cada punto, mirar sus puntos vecinos a distancia 20 (y-20 a y+20, x-20 a x+20) y ya.
Perdi unos minutos porque no me di cuenta que (y-20 a y+20, x-20 a x+20) incluye puntos a mas de 20 de distancia.
Codigo (despues de limpiarlo xD)
def solve(rawinput):
mat: dict[tuple[int, int], str]
mat, start, end = read(rawinput)
# Find normal path
legal_path = [start]
legal_cost = {start: 0}
while (p := legal_path[-1]) != end:
for p2 in neighbors4(*p):
if p2 not in legal_cost and mat[p2] == ".":
legal_path.append(p2)
legal_cost[p2] = len(legal_path) - 1
print("Legal Cost =", legal_cost[start])
# Find cheats
for k in (2, 20):
ans = 0
for current_cost, (y, x) in enumerate(legal_path):
for y2 in range(y - k, y + k + 1):
for x2 in range(x - k, x + k + 1):
p2 = (y2, x2)
dist = abs(y - y2) + abs(x - x2)
if dist > k or p2 not in legal_cost:
continue
new_cost = current_cost + dist
saved = legal_cost[p2] - new_cost
if saved >= 100:
ans += 1
print(f"max cheats={k:<2} -> {ans}")