Matrices y grillas
Qué vamos a ver hoy
| Min | Tema |
|---|---|
| 0–15 | Crear y leer matrices |
| 15–30 | Recorridos: filas, columnas, diagonales y espiral |
| 30–40 | Transpuesta, rotación y reflejos |
| 40–60 | Vecinos con dx, dy y límites |
| 60–70 | Multiplicación de matrices y aplanar |
| 70–80 | Beautiful Matrix y Number Spiral |
| 80–85 | Entrada y salida rápida, con Number Spiral como caso |
| 85–90 | Formato del Contest 1 |
Crear y leer matrices
Una matriz en Python es una lista de listas.
Por qué [[0]*m]*n falla
M = [[0] * m] * n # n referencias a la MISMA fila
Haga clic en una celda. Las n filas son la misma lista, así que el cambio aparece en todas.
M = [[0] * m for _ in range(n)] # n filas distintas
Cada vuelta del for crea una lista nueva. El cambio aparece en una sola celda.
Matrices de números y grillas de caracteres
# n filas de m números n, m = map(int, input().split()) M = [list(map(int, input().split())) for _ in range(n)] # n filas de caracteres '#' y '.' g = [input() for _ in range(n)] g[i][j] # carácter de la fila i, columna j # si hay que modificar la grilla g = [list(input()) for _ in range(n)]
Una fila leída como string no se puede modificar. Para cambiar celdas se lee como lista.
Recorridos
El orden en que se visitan las celdas.
Seis formas de recorrer una matriz
Cada diagonal tiene un valor constante
Haga clic en una celda.
- En la diagonal que baja a la derecha,
i − jes constante. La principal esi − j = 0. - En la diagonal que baja a la izquierda,
i + jes constante. La secundaria esi + j = n − 1. - Hay
n + m − 1diagonales de cada tipo. - Para agruparlas:
diag[i - j + m - 1]yanti[i + j].
En el problema de las N reinas (semana 11) se usan estos índices para saber si dos reinas se atacan.
Transformaciones
Transpuesta, rotaciones y reflejos con zip y slicing.
Transponer, rotar y reflejar
Vecinos
Moverse a las celdas de al lado sin salirse de la grilla.
Vecinos con dx, dy
DX = [-1, 1, 0, 0] # arriba, abajo,
DY = [0, 0, -1, 1] # izquierda, derecha
for d in range(4):
ni, nj = i + DX[d], j + DY[d]
if 0 <= ni < n and 0 <= nj < m and g[ni][nj] != '#':
# (ni, nj) es un vecino válido
...
# 8 direcciones: todos los (di, dj) salvo (0, 0)
for di in (-1, 0, 1):
for dj in (-1, 0, 1):
if di == 0 and dj == 0:
continue
...
Primero se revisan los límites y después la celda. Si se revisa la celda primero, g[-1] no da error: lee la última fila.
Multiplicación y aplanado
Producto de matrices en O(n³) y matrices guardadas en una sola lista.
Fila de A por columna de B
# A es n×m, B es m×p, C es n×p
C = [[0] * p for _ in range(n)]
for i in range(n):
for j in range(p):
s = 0
for k in range(m):
s += A[i][k] * B[k][j]
C[i][j] = s
Tres bucles anidados: O(n · m · p). Con matrices n × n es O(n³).
Con n = 500 son 1.25·10⁸ multiplicaciones. Con PyPy tardó 0.19 s en un Apple M4; en el juez queda más cerca del límite.
Extensión: elevando una matriz 2 × 2 a la potencia k se calcula el k-ésimo Fibonacci en O(log k). Se retoma como reto en la semana 8.
Una matriz en una sola lista
k = i * m + j # (i, j) → k i, j = divmod(k, m) # k → (i, j) vis = bytearray(n * m) # visitados, 1 byte por celda
Cada celda queda representada por un solo entero. En BFS (semana 12) la cola guarda enteros en lugar de tuplas (i, j).
En PyPy también es más rápido: sumar los 4 vecinos de 10⁶ celdas tardó 0.005 s con M[i][j] y 0.002 s aplanado.
Problemas
Beautiful Matrix, Number Spiral y grillas de AtCoder.
Llevar el 1 al centro
Una matriz 5 × 5 tiene un solo 1. Cada movimiento intercambia dos filas vecinas o dos columnas vecinas. ¿Cuántos movimientos hacen falta para llevar el 1 al centro?
def main():
for i in range(5):
fila = input().split()
for j in range(5):
if fila[j] == "1":
print(abs(i - 2) + abs(j - 2))
main()
Es la distancia Manhattan al centro (2, 2): cada movimiento cambia la fila o la columna en 1.
El valor en la fila y, columna x
Haga clic en una celda. Las capas se colorean por paridad.
def valor(y, x):
z = max(y, x) # capa
if z % 2 == 0:
if y == z:
return z * z - x + 1
return (z - 1) ** 2 + y
else:
if x == z:
return z * z - y + 1
return (z - 1) ** 2 + x
y, x ≤ 10⁹ y hasta 10⁵ consultas: no se puede construir la grilla. Cada consulta es O(1). El resultado llega a 10¹⁸ y Python no se desborda.
Number Spiral con 10⁵ consultas
input() y print
t = int(input())
for _ in range(t):
y, x = map(int, input().split())
# ... calcular v ...
print(v)
import sys
input = sys.stdin.readline
t = int(input())
ans = []
for _ in range(t):
y, x = map(int, input().split())
# ... calcular v ...
ans.append(v)
sys.stdout.write("\n".join(map(str, ans)) + "\n")
input() + print0.14 sEl cálculo es el mismo en los dos. Cambia solo la forma de leer y escribir. Medido con PyPy 3.11, Apple M4, con 10⁵ consultas aleatorias. El juez suele ser más lento.
Por qué es más rápida
input()hace trabajo extra en cada llamada: entre otras cosas, vacía la salida pendiente antes de leer.sys.stdin.readlinelee la línea directamente.printhace una escritura por cada línea.joinarma un solo string ysys.stdout.writelo escribe una vez.- La diferencia se nota desde unas 10⁵ líneas. Con pocas líneas da lo mismo.
# leer toda la entrada de una vez data = sys.stdin.buffer.read().split() t = int(data[0]) # data[1], data[2], ... son bytes; int() los convierte
Leer 10⁶ enteros, uno por línea
input()0.13 sreadline0.07 sEscribir 10⁶ enteros, uno por línea
print0.025 sjoin + write0.014 sPyPy 3.11, Apple M4, salida a /dev/null.
Dos problemas B de grillas
ABC075 B · Minesweeper
Cambiar cada . por la cantidad de # entre sus 8 vecinos.
for i in range(h):
fila = []
for j in range(w):
if g[i][j] == "#":
fila.append("#")
continue
c = 0
for di in (-1, 0, 1):
for dj in (-1, 0, 1):
ni, nj = i + di, j + dj
if 0 <= ni < h and 0 <= nj < w and g[ni][nj] == "#":
c += 1
fila.append(str(c))
print("".join(fila))
ABC107 B · Grid Compression
Quitar las filas y columnas que solo tienen ..
g = [f for f in g if "#" in f] # filas cols = [c for c in zip(*g) if "#" in c] # columnas g = ["".join(f) for f in zip(*cols)] # volver a filas
Las columnas se filtran transponiendo con zip(*g), filtrando y transponiendo otra vez.
Problemas de matrices
Contest 1
- Cubre las semanas 1 a 4.
- Dura 90 minutos y es presencial.
- Tiene de 4 a 6 problemas, ordenados de menor a mayor dificultad.
- Vale el 5 % de la nota.
- Se hace en el grupo del curso: [enlace pendiente].
Qué repasar
- Leer la entrada y escribir la salida con el formato pedido.
- Tabla de restricciones contra complejidad.
- Slicing,
join, frecuencias. - Crear matrices, vecinos y límites.
Reglas para matrices en Python
- Crear con
[[0] * m for _ in range(n)]. - Leer grillas con
input(), y como lista si hay que modificarlas. - Revisar los límites antes de acceder a una celda vecina.
- Transponer con
zip(*M)y rotar conzip(*M[::-1]). - Guardar la fila en una variable dentro de los bucles internos.
- Con 10⁵ líneas o más, leer con
sys.stdin.readliney escribir una sola vez conjoin.