Notebook Python
Notebook Python
5 Algoritmos 8
Contents 5.1 Divide and Conqueer D&C . . . . . . . . . 8
Team: –ejemplo–
3.1 Leer grafos . . . . . . . . . . . . . . . 3 6.5 Combinatoria . . . . . . . . . . . . . . . 10
3.1.1 [Link] . . . . . . . . . . . . . . 3 6.5.1 [Link] . . . . . . . . . . 10
3.2 BFS: Busqueda en Anchura . . . . . . . . . 3 6.5.2 Precomputo O(N ) . . . . . . . . . . 11
3.2.1 [Link] . . . . . . . . . . . . . . 3 6.6 Elementos de Geometría . . . . . . . . . . 11
3.3 Bipartir un grafo . . . . . . . . . . . . 3 6.6.1 [Link] . . . . . . . . . . . . . 11
3.3.1 [Link] . . . . . . . . . . . . 3 6.6.2 poligono_convexo.py . . . . . . . . 11
3.3.2 bipartir_2.py . . . . . . . . . . . 3 6.7 Capsula Convexa . . . . . . . . . . . . . 11
3.4 Camino Mínimo . . . . . . . . . . . . . . 3
6.7.1 capsula_convexa.py . . . . . . . . 11
3.4.1 [Link] . . . . . . . . . . . . 3
6.8 Teoría de juegos . . . . . . . . . . . . . 12
3.4.2 floyd_warshall.py . . . . . . . . . 4
6.8.1 [Link] . . . . . . . . . . . . . . 12
3.4.3 bellman_ford.py . . . . . . . . . . 4
(UNLaM)
3.4.4 [Link] . . . . . . . . . . . . . . 4 6.9 Identities . . . . . . . . . . . . . . . . 12
3.5 Union Find . . . . . . . . . . . . . . . . 4 6.10 Rodrigues Rotation Formula . . . . . . . . 12
3.5.1 Small To Large . . . . . . . . . . 4 6.11 FFT y NTT . . . . . . . . . . . . . . . . 12
3.5.2 Path Compression y Union by Size . 4 6.11.1 [Link] . . . . . . . . . . . . . . 12
3.6 MST: Árbol Generador Mínimo . . . . . . . 4 6.11.2 [Link] . . . . . . . . . . . . . . 12
3.6.1 [Link] . . . . . . . . . . . . 4
3.6.2 [Link] . . . . . . . . . . . . . . 5 7 Strings 13
3.7 Componentes Fuertemente Conexas . . . . . 5 7.1 Bordes . . . . . . . . . . . . . . . . . . 13
3.7.1 kosaraju_iterativo.py . . . . . . . 5 7.1.1 [Link] . . . . . . . . . . . . . 13
3.7.2 tarjan_iterativo.py . . . . . . . . 5 7.2 Función Z . . . . . . . . . . . . . . . . 13
3.7.3 grafo_condensado.py . . . . . . . . 6 7.2.1 funcion_z.py . . . . . . . . . . . 13
3.7.4 2_SAT.py . . . . . . . . . . . . . 6 7.3 Manacher (Palindromos) . . . . . . . . . . 13
3.8 Components Biconexas, Puentes y Puntos de
7.3.1 [Link] . . . . . . . . . . . . 13
Articulación . . . . . . . . . . . . . . . 6
7.4 Trie . . . . . . . . . . . . . . . . . . . 13
3.8.1 componentes_biconexas.py . . . . . 6
3.9 LCA: Ancestro Común Menor . . . . . . . . 7 7.4.1 [Link] . . . . . . . . . . . . . . 13
3.9.1 binary_lifting_funcional.py . . . . 7
8 Other 14
3.9.2 binary_lifting_lca.py . . . . . . . 7
3.9.3 lca_sparse_table.py . . . . . . . . 7
9 Tablas y Cotas 14
4 Estructuras de Datos 7 9.1 Divisores . . . . . . . . . . . . . . . . 14
Page 1 of 14
Programación Dinámica
Setup 2.3.1 sub_set_sum.py
En esta sección irán los códigos básicos, vistos en la # Solución al problema Cambio de Monedas con DP
categoría Generales del árbol de correlatividades. # Problema: Dados:
# * un conjunto de monedas C = {c1, c2, ..., ck}
Busqueda Binaria # * Un valor V,
# Determinar el mínimo número de monedas de C necesarias para sumar V.
2.1.1 lower_bound.py def cambio_monedas(C, V): #O(n * V)
n = len(C)
# Devuelve el índice del primer elemento mayor o igual a x A = [0] + [float('inf')] * V
# en un arreglo ordenado for i in range(1, V + 1):
def lower_bound(V, x): for j in range(n):
l, r = -1, len(V) if i >= C[j]:
while l < r: # V[l] < x <= V[r] A[i] = min(A[i], A[i - C[j]] + 1)
Team: –ejemplo–
m = (l + r) // 2 return A
if V[m] < x: # A[i] = mínimo número de monedas de C necesarias para sumar i
l = m
else:
r = m Recurrencias Lineales
return r 2.4.1 recurrena_lineal.py
2.1.2 upper_bound.py
# Problema: Dada una recurrencia lineal de la forma
# A[i] = c1 * A[i - 1] + c2 * A[i - 2] + ... + ck * A[i - k]
# Devuelve el índice del primer elemento mayor a x # con A[0], A[1], ..., A[k - 1] dados, determinar A[n] para n >= k.
# en un arreglo ordenado
# ej: Fibonacci(n) = recurrencia([0,1],[1,1],n)
def upper_bound(V, x): # IMPORTANTE: no olvidar el modulo
l, r = -1, len(V)
while l < r: # V[l] <= x < V[r] def recurrencia(A, C, n, mod = int(1e9+7)): # O(n * k)
(UNLaM)
m = (l + r) // 2 k = len(C)
if V[m] <= x: if n < k:
l = m return A[n]
else: A = A + [0] * (n - k + 1)
r = m for i in range(k, n + 1):
return r A[i] = sum(C[j] * A[i - j] for j in range(k)) % mod
return A[n]
Tabla Aditiva # No lo vimos en clase, pero existe una solución más eficiente en
2.2.1 tabla_aditiva.py # O(k^2 * log(n)) usando exponenciación binaria de polinomios.
def recurrencia(A, C, n, mod = int(1e9+7)): # O(k^2 * log(n))
k = len(C)
def crear(V):
if n < k:
n = len(V)
return A[n]
A = [0] * (n + 1)
A = A + [0] * (n - k + 1)
for i in range(n):
def mult(A, B): # Producto de polinomios
A[i + 1] = A[i] + V[i]
n = len(A)
return A #A[i] = sum(V[:i))
C = [0] * n
def consulta(A, l, r): for i in range(n):
return A[r] - A[l] #sum(V[l:r)) for j in range(n):
C[i] += A[j] * B[i - j]
2.2.2 tabla_aditiva_2D.py C[i] %= mod
return C
def exp(A, n): # Potencia rápida de polinomios
# Permite crear y consultar una tabla aditiva para matrices 2D en O(n if n == 1:
* m) y O(1) respectivamente. return A
def crear(M): #M: matriz, O(n * m) if n % 2 == 0:
n, m = len(M), len(M[0]) return exp(mult(A, A), n // 2)
A = [[0] * (m + 1) for _ in range(n + 1)] return mult(A, exp(A, n - 1))
for i in range(n): C = [0] * (k * k)
Page 2 of 14
Team: –ejemplo–
return (False,[])
Leer grafos it = it+1
3.1.1 [Link] return (True,color)
3.3.2 bipartir_2.py
# Notar que el codigo no cambia si el grafo es ponderado o no
def leer_lista_aristas(m):
return [ # Dado un grafo con aristas con etiquetas 0 y 1
list(map(lambda x : int(x)-1, input().split())) # * Las etiquetas 0 indican que ambos nodos deben tener el mismo color
for _ in range(m) # * Las etiquetas 1 indican que ambos nodos deben tener colores
] distintos
# Decide si es posible colorear el grafo con dos colores
# ady[u] son los nodos a los que llegan aristas desde u # de tal forma que se cumplan todas las etiquetas
def leer_lista_adyacencia(n,m): # Si se puede, retorna True y la lista de colores
# reutilizo código # Si no, retorna False y una lista vacía
aristas = leer_lista_aristas(m)
(UNLaM)
def Bipartir2(ady: list[tuple[int,int]]) -> tuple[bool, list[int]] :
ady = [[] for _ in range(n)]
for arista in aristas: # arista es (vecino, peso)
u = arista[0] N = len(ady)
v = arista[1] color = [-1]*N
for inicio in range(0,N):
# Para grafo ponderado if color[inicio] != -1: continue
ady[u].append([v]+arista[1:]) color[inicio] = 0
ady[v].append([v]+arista[1:]) # no dirigido bolsa, it = [inicio], 0
# Para grafo no ponderado while it < len(bolsa):
ady[u].append(v) nodo = bolsa[it]
for vecino, peso in ady[nodo]:
ady[v].append(u) # no dirigido
if color[vecino]==-1:
return ady color[vecino] = peso ^ color[nodo]
[Link](vecino)
# inc[u] son las aristas incidentes al nodo u elif color[vecino] == color[nodo] ^ peso:
def leer_lista_incidencia(n,m): return (False, [])
aristas = leer_lista_aristas(m)
inc = [[] for _ in range(n)] it = it+1
for i,arista in enumerate(aristas): return (True,color)
u = arista[0]
v = arista[1] Camino Mínimo
inc[u].append(i)
inc[v].append(i) # no dirigido
3.4.1 [Link]
return inc, aristas
import heapq
BFS: Busqueda en Anchura # Implementación O(M * log N) de Dijkstra con heap
3.2.1 [Link] # Es en casi todo caso lo recomendable
# Recibe un nodo de origen y una lista de adyacencia
# Devuelve la distancia mínima de origen a cada nodo
Page 3 of 14
for u in range(len(G)):
heap = [] for v, w in G[u]:
Team: –ejemplo–
distancias[vecino] = distancias[siguiente] + distancia
# Implementa union find utilizando la técnica de
return distancias
# small to large
# Notar que n se debe definir antes en el código
3.4.2 floyd_warshall.py
id = [i for i in range(n)]
# Inicialmente cada nodo esta en su propia componente
# Calcula la distancia mínima de cada nodo a cada nodo
# Soporta pesos negativos cmp = [[i] for i in range(n)]
# Retorna una matriz de distancias # Retorna True si se unieron los nodos,
# O(N^3) # False si ya estaban en la misma componente
def FloydWarshall(G : list[list[tuple[int,int]]]): def union(u, v):
distancias = [[float('inf')] * len(G) for _ in range(len(G))] u, v = id[u], id[v]
for u in range(len(G)): if u == v: return False # No se los unio
distancias[u][u] = 0 if len(cmp[u]) < len(cmp[v]): u, v = v, u
for v, w in G[u]: for x in cmp[v]:
(UNLaM)
distancias[u][v] = w cmp[u].append(x)
for k in range(len(G)): id[x] = u
return True
for i in range(len(G)):
for j in range(len(G)): 3.5.2 Path Compression y Union by Size
distancias[i][j] = min(distancias[i][j], distancias[i][k] +
distancias[k][j]) # Implementa union find con las optimizaciones
return distancias # de path compression y union by size comentadas
# en la clase
3.4.3 bellman_ford.py # Notar que n se debe definir antes en el código
pad = [i for i in range(n)]
# Recibe un nodo de origen, una lista de adyacencia y una longitud L # Inicialmente cada nodo es su propio padre
# Calcula para cada nodo y longitud la distancia mínima del origen a sz = [1] * n
# ese nodo con exactamente esa cantidad de aristas. # tamaño de las componentes
# Soporta pesos negativos.
def find(u):
# O((N+M) * L) tiempo, O(N*L) memoria
visto = []
def BellmanFord(origen : int, G : list[list[tuple[int,int]]], L : int) while u != pad[u]:
-> list[list[int]]: [Link](u)
distancias = [ [float('inf')] * len(G) for _ in range(L+1) ] u = pad[u]
distancias[0][origen] = 0 for x in visto:
for l in range(L): pad[x] = u
for u in range(len(G)): return u
for v, w in G[u]: # Retorna True si se unieron los nodos,
distancias[l+1][v] = min(distancias[l+1][v], distancias[l][u] # False si ya estaban en la misma componente
+ w) def union(u, v):
return distancias u, v = find(u), find(v)
if u == v: return False
# Similar a la anterior pero retorna para cada nodo
if sz[u] < sz[v]: u, v = v, u
Page 4 of 14
Team: –ejemplo–
costo = 0
mst = [] return cmp
n = max([a[0] for a in g] + [a[1] for a in g]) + 1
adj = [[] for _ in range(n)] 3.7.2 tarjan_iterativo.py
for a in g:
adj[a[0]].append((a[1],a[2])) def Tarjan(g : list[list[int]]) -> list[int] :
adj[a[1]].append((a[0],a[2])) n = len(g)
used = [False] * n cmp = [-1] * n
while heap: cmp_id = 0
w,u,v = [Link](heap) tiempo = 0
if used[v]: continue
used[v] = True entrada = [-1] * n
if u != -1: min_entrada = [-1] * n
costo += w arista = [0]*n
[Link]((u,v,w))
def dfs(u):
(UNLaM)
for x in adj[v]: nonlocal cmp_id
if not used[x[0]]: nonlocal tiempo
[Link](heap,(x[1],v,x[0]))
return costo, mst pila = [u]
pila_cmp = []
Componentes Fuertemente Conexas while pila:
u = pila[-1]
3.7.1 kosaraju_iterativo.py [Link]()
if entrada[u] == -1:
entrada[u] = tiempo
# Recibe la lista de adyacencia de un grafo dirigido
min_entrada[u] = tiempo
# Devuelve una lista con el id de la componente
tiempo += 1
# fuertemente conexa a la que pertenece cada nodo
pila_cmp.append(u)
# O(N+M) tiempo
while arista[u] < len(g[u]):
def Kosaraju(g : list[list[int]]) -> list[int] :
v = g[u][arista[u]]
n = len(g)
if entrada[v] == -1:
ord = []
[Link](u)
# Ordeno usando simil BFS [Link](v)
d_in = [0] * n break
for u in range(n): elif entrada[v] > entrada[u]:
for v in g[u]: min_entrada[u] = min(min_entrada[u], min_entrada[v
d_in[v] += 1 ])
elif cmp[v] == -1:
visitados = [False] * n
min_entrada[u] = min(min_entrada[u], entrada[v])
arista = [0] * n
arista[u] += 1
# Hago un pseudo-toposort con DFS iterativo if arista[u] == len(g[u]) and entrada[u] == min_entrada[u
def dfs(ini): ]:
pila = [ini] while True:
while pila: v = pila_cmp.pop()
Page 5 of 14
Team: –ejemplo–
# donde cada cláusula es una
# tupla de dos elementos. Si el primer elemento de la padre = [-1] * n
# tupla es positivo, se afirma la variable correspondiente.
# Si el segundo elemento de la tupla es positivo, se llegada = [-1] * n
# afirma la variable correspondiente. Si el primer elemento min_alcanza = [-1] * n
# de la tupla es negativo, se niega la variable correspondiente. tiempo = 0
# Si el segundo elemento de la tupla es negativo, se niega la pila = []
# variable correspondiente. indice = [0] * n
componente = 0
# La función retorna una lista de booleanos, donde el i-ésimo
# booleano indica si la variable i debe ser verdadera o falsa. def DFS(u):
# Si no existe una asignación que haga verdadera a la fórmula, nonlocal tiempo, componente
# retorna una lista vacía. pila_dfs = [u]
# La función tiene complejidad O(N+M), donde while len(pila_dfs) > 0:
# N es el número de variables y u = pila_dfs.pop()
# M es el número de cláusulas.
(UNLaM)
# Ejemplo de uso: if llegada[u] == -1:
# f = [(1,2),(-1,-2),(1,-2),(-1,2)] llegada[u] = tiempo
# print(SAT2(2,f)) # [True, True] min_alcanza[u] = tiempo
# print(SAT2(2,[(1,2),(1,-2),(-1,2),(-1,-2)])) # [] tiempo += 1
# Necesita tener implementado Condensado y Toposort ar = g[u][indice[u]]
def SAT2(n : int, f : list[tuple[int,int]]) -> list[bool]: v = ars[ar][0] + ars[ar][1] - u
# Formato input: >0 afirmo variable, <0 niego variable if ar != padre[u]:
g = [[] for _ in range(2*n)] if llegada[v] == -1:
def neg(x): padre[v] = ar
return x+n if x<n else x-n pila_dfs.append(u)
pila_dfs.append(v)
# Construyo el grafo de implicancias que modela el problema
[Link](ar)
for (p1, p2) in f: continue
x1 = p1 - 1 if p1>0 else neg(-p1-1)
x2 = p2 - 1 if p2>0 else neg(-p2-1) if padre[v] == ar:
g[neg(x1)].append(x2) if min_alcanza[v] > llegada[u]: puente[ar] = True
g[neg(x2)].append(x1) if min_alcanza[v] >= llegada[u]:
punto[u] += 1
# Calculo el grafo condensado last = [Link]()
(gc, cmp) = Condensado(g) while last != ar:
componentes = [[] for _ in range(len(gc))] cmp[last] = componente
for u in range(2*n): last = [Link]()
componentes[cmp[u]].append(u) cmp[ar] = componente
componente += 1
# Reviso que no haya contradicción
min_alcanza[u] = min(min_alcanza[u], min_alcanza[v])
for i in range(n):
elif llegada[v] < llegada[u]:
if cmp[i]==cmp[i+n]:
[Link](ar)
return []
min_alcanza[u] = min(min_alcanza[u], llegada[v])
# Asigno valores a las variables
Page 6 of 14
Team: –ejemplo–
# Usa Sparse Table
3.9.2 binary_lifting_lca.py class LCA_ST:
# O(NlogN)
# Estructura de datos que almacena el Binary def __init__(self, g : list[list[int]], raiz : int = 0):
# Lifting de un árbol self.n = len(g)
self.in_order, [Link], self.a_vec = generar(g, raiz)
class BinaryLifting: [Link] = st_build(self.a_vec)
def __init__(self, g : list[list[int]], \ def lca(self, u : int, v : int) -> int:
raiz : int = 0, l : int = 0): l, r = self.in_order[u], self.in_order[v]
self.n = len(g) if l > r:
self.l = max(l,self.n.bit_length()) l, r = r, l
[Link] = [[-1] * self.n for _ in range(self.l)] return st_query([Link], l, r)[1]
[Link] = [0] * self.n
[Link][0][raiz] = raiz
Estructuras de Datos
(UNLaM)
pila = [raiz]
while pila:
u = pila[-1]
[Link]() Árbol de Segmentos
for v in g[u]:
if [Link][0][v] == -1:
4.1.1 segment_tree.py
[Link][0][v] = u
[Link][v] = [Link][u] + 1 # Árbol de Segmentos
[Link](v) # Se inicializa con un vector V de n valores
for i in range(1, self.l): # Permite aplicar una operacion op() asociativa a
for u in range(self.n): # un rango [l,r] de V.
[Link][i][u] =\ # Se deben definir:
[Link][i - 1][[Link][i - 1][u]] # * La operación op(a, b)
# * El valor neutro de la operación
def kancestro(self, u : int, k : int) -> int: # Complejidad (llamados a op):
for i in range(self.l): # * Construcción: O(n)
if k & (1 << i): # * Consulta: O(log(n))
u = [Link][i][u] # * Actualización: O(log(n))
return u
class SegmentTree:
def lca(self, u : int, v : int) -> int:
# Ejemplo de posible operacion
if [Link][u] > [Link][v]:
u, v = v, u def Op(self, a, b):
v = [Link](v, [Link][v] - [Link][u]) return a + b
if u == v: # Ejemplo del neutro de la operación
return u neutro = 0
for i in range(self.l - 1, -1, -1): def __init__(self, V):
if [Link][i][u] != [Link][i][v]: # La función __init__ nos permite crear un nuevo elemento de
u = [Link][i][u] la clase
v = [Link][i][v] n = len(V)
Page 7 of 14
Team: –ejemplo–
st[i][j] = operation(st[i][j - 1], st[i + (1 << (j - 1))][
4.1.2 segment_tree_lazy_creation.py j - 1])
return st
# O(1): Usar si la operación es idempotente (ej: mínimo, máximo, and,
# Árbol de Segmentos or)
# Se inicializa con un entero n que índica el tamaño del def st_query(st : list[list[any]], l : int, r : int) -> any:
# dominio. Inicialmente todos los valores son el neutro j = r - l
# de la operación
k = j.bit_length() - 1
# Permite trabajar con un dominio arbitrariamente grande
return operation(st[l][k], st[r - (1 << k)][k])
# Permite aplicar una operacion op() asociativa a
# un rango [l,r] de V. # O(log(n)): Usar si la operación no es idempotente (ej: suma,
# Se deben definir: producto)
# * La operación op(a, b) def st_query(st : list[list[any]], l : int, r : int) -> any:
# * El valor neutro de la operación res = None
# Complejidad (llamados a op): for k in range(len(st[0]) - 1, -1, -1):
(UNLaM)
# * Construcción: O(n) if l + (1 << k) <= r:
# * Consulta: O(log(n)) if res == None: res = st[l][k]
# * Actualización: O(log(n)) else: operation(st[l][k], st_query(st, l + (1 << k), r))
l += 1 << k
class SegmentTreeLazy: return res
# Ejemplo de posible operacion
def Op(self, a, b):
return a + b
# Ejemplo del neutro de la operación
neutro = 0
Algoritmos
def __init__(self, n): Divide and Conqueer D&C
# La función __init__ nos permite crear un nuevo elemento de
la clase 5.1.1 merge_sort.py
[Link] = 1
while [Link] < n: # Ejemplo de problema resuelto con D&C
[Link] *= 2 # Ordena un vector en O(nlogn)
[Link] = dict() # Realiza log(n) capas de recursión
def Consulta(self, lq, rq, nodo = 1, l = 0, r = - 1): def MergeSort(V : list[any]) -> list[any] :
if r == -1: r = [Link]-1 if len(V) < 2: return V
# Si r no fue dado, se asume que es el largo del árbol - 1 m = len(V) // 2
if l > rq or r < lq or nodo not in [Link]: L = MergeSort(V[:m])
# Si el intervalo [l, r] está completamente fuera de [lq, R = MergeSort(V[m:])
rq] i,j = 0,0
return [Link] for k in range(len(V)):
if lq <= l and r <= rq: if i >= len(L):
# Si el intervalo [l, r] está completamente dentro de [lq, V[k] = R[j]
rq] j += 1
return [Link][nodo] elif j >= len(R):
m = (l + r) // 2 V[k] = L[i]
Page 8 of 14
Team: –ejemplo–
for i in range(1, len(V)-k+1):
T (n) ∈ Θ(n × log n). j = i+k
cantidad -= 1 if [Link](V[i],0) == 1 else 0
– Si f (n) ∈ Ω(nc ) con c > logb (a) y existe cantidad += 1 if [Link](V[j-1],0) == 0 else 0
k < 1 tal que para n suficientemente grande, histo[V[i]] = [Link](V[i],0) - 1
a × f (n/b) ≤ k × f (n), histo[V[j-1]] = [Link](V[j-1],0) + 1
res[i] = cantidad
entonces T (n) ∈ Θ(f (n)). Ejemplo: return res
T (n) = 2 × T (n/2) + n2 , entonces
T (n) ∈ Θ(n2 ). Algoritmo de Mo
5.3.1 mo_plantilla.py
• Análisis amortizado: Como en otros algoritmos,
puede haber factores que limiten la cantidad # Plantilla para aplicar el algoritmo de Mo a cualquier problema
de estados de manera ad-hoc. En el segundo prob- # Requisitos
(UNLaM)
# - Se realizán consultas de forma asincronica
lema # - No hay actualizaciones
de ejemplo, se observa que cada estado elimina # - La función AgregarElemento debe ser implementada
un elemento, y cada elemento es eliminado por un # - La función EliminarElemento debe ser implementada
# - La variable neutro debe ser definida
único estado. Por lo tanto, hay como máximo n es- # CUIDADO: Si la operación no es conmutativa, deben implementar
tados distintos. # versiones por izquierda y derecha de
# AgregarElemento y EliminarElemento
# para evitar errores
Técnica de 2 Punteros # Complejidad: O((N+Q) * sqrt(N) * O(Agregar/Eliminar Elemento))
# En Python es probable que de TLE, en C++ no debería
5.2.1 dos_punteros.py
# Formato del input: [l,r)
while j > r:
j -= 1 V (GCD(A, B)) = min(V (A), V (B))
Team: –ejemplo–
suma -= V[j] divisores = []
res[idx] = suma for i in range(1, N):
return res if i * i > N: # Es mejor que buscar calcular la raiz cuadrada
de antes
break
Matematicas if N % i == 0: # Si i es divisor
[Link](i) # Lo añadimos
if i != N // i: # Si i no es la raiz cuadrada
Números Primos [Link](N // i) # Añadimos el otro divisor
return divisores
6.1.1 criba_eratostenes.py
Divisor Común Mayor
# Calcula la criba de Eratóstenes hasta N 6.3.1 [Link]
# Para cada número 0 <= i <= N, criba[i] es True
# si i es primo, False en caso contrario
(UNLaM)
# O(Nlog(log(N)) # Calcula el Divisor Común Mayor de a y b
# O(log(min(a,b)))
def Eratostenes(N:int) -> list[bool]: # Notar que es una operación:
criba = [False] * 2 + [True] * (N - 1) # - Asociativa
# El 0 y el 1 sabemos que no lo son # - Conmutativa
for p in range(2, N + 1): # - Tiene elemento neutro: 0
# Iteramos los números de 2 a N # - No tiene inverso
if criba[p]: # Si p es primo # - Idempotente (gcd(a,a) = a)
for i in range(p * p, N + 1, p):
# Recorremos de a saltos de longitud p def gcd(a : int, b: int) -> int:
while b != 0:
criba[i] = False a, b = b, a % b
return criba
return a
# para listar primos
primos = Eratostenes(N) Aritmetica Modular
print(list(filter(lambda x: primos[x], range(N+1))))
6.4.1 aritmetica_modular.py
Pensemos que cada número natural (excluido el 0) es
un vector infinito de posiciones naturales (inclu- # Realizar las operaciones con
# los enteros modulo m
ido el 0). En este caso, V (N )[i] indica el exponente
del i-ésimo primo en la factorización del número N . def SumaMod(a, b, m):
return (a+b)%m
Llamemos V (N ) a la representación vectorial de N .
Hacer A × B como números es sumar sus respectivos vec- def RestaMod(a, b, m):
return ((a-b)%m+m)%m
tores. Es decir, V (A × B) = V (A) + V (B).
A
def MultMod(a, b, m):
Análogamente, se tiene que V B = V (A) − V (B). return (a*b)%m
Lo interesante es notar que si un número divide a
otro, entonces tiene un exponente menor o igual en Combinatoria
Page 10 of 14
# Usamos memorización para evitar calculos innecesarios # Recordar que sum nos devuelve la suma de todos los elementos de un
_factorial = [1] iterable
def Combinatoria(n : int, r : int, mod : int) -> int: def producto_por_escalar(p : list[float], k : float) -> list[float]:
if r > n: return 0 # Retorna el producto de un punto por un escalar
return MultMod( return [k * x for x in p]
MultMod(Factorial(n),inv(Factorial(r),mod),mod),
def resta_puntos(p1 : list[float], p2 : list[float]) -> list[float]:
inv(Factorial(n-r),mod),mod)
# Retorna la resta de dos puntos
# Variaciones con Repetición return [p1[i] - p2[i] for i in range(len(p1))]
# Cantidad de formas de elegir r elementos # Recordar que range(n) nos devuelve un iterable con los números del 0
# de un conjunto de n elementos con repetición al n-1
# Necesario definir mod
# Calcular la distancia entre 2 puntos
def VR(n : int, r : int, mod : int) -> int: def distancia(p1 : list[float], p2 : list[float]) -> float:
return PotenciaMod(n, r, mod) # Retorna la distancia entre dos puntos
return norma(resta_puntos(p1, p2))
# Variaciones sin repetición
# Cantidad de formas de elegir r elementos def distancia2(p1 : list[float], p2 : list[float]) -> float:
# de un conjunto de n elementos sin repetición # Retorna el cuadrado de la distancia entre dos puntos
# Necesario definir mod return norma2(resta_puntos(p1, p2))
def V(n : int, r : int, mod : int) -> int: # Calcular el producto punto entre dos vectores
return MultMod(Factorial(n),inv(Factorial(n-r),mod),mod) def producto_punto(p1 : list[float], p2 : list[float]) -> float:
# Retorna el producto punto entre dos puntos
# Permutaciones con repetición
return sum([p1[i] * p2[i] for i in range(len(p1))])
# Cantidad de formas de ordenar un multiconjunto
# con n1, n2, ..., n_k repeticiones de los elementos
Team: –ejemplo–
# Calcular el producto cruz entre dos vectores en R^2
# 1, 2, ..., k def producto_cruz(p1,p2):
def P(ns : list[int], mod : int) -> int: return p1[0]*p2[1]-p1[1]*p2[0]
n = sum(ns) # Calcular el producto cruz entre dos vectores en R^3
res = Factorial(n,mod) def producto_cruz3(p1,p2):
for a in ns: return [p1[1]*p2[2]-p1[2]*p2[1],
res = MultMod(res,inv(Factorial(a,mod),mod),mod) p1[2]*p2[0]-p1[0]*p2[2],
return res p1[0]*p2[1]-p1[1]*p2[0]]
# Recordar:
# Si tengo X con OX ordenes validos e Y con OY 6.6.2 poligono_convexo.py
# ordenes validos, puedo unirlos y si no hay
# restrcciones entre sus elementos
# (X U Y) tiene C(|X|+|Y|,|X|) * OX * OY ordenes # Funciones para trabajar con poligonos convexos
# validos # Dados los puntos de un poligono convexo en orden anti-horario
(UNLaM)
# retorna el area del poligono. (Si están en sentido
6.5.2 Precomputo O(N ) # horario el area es negativa)
def Area_Poligono(puntos): # se asumen ordenados
p = puntos[0]
# Dado un N y un modulo mod, computa en O(N)
# los factoriales y sus inversos modulo mod return sum(producto_cruz(resta_punto(puntos[i],p),
# hasta N inclusive resta_punto(puntos[(i+1)%len(puntos)],p))
# idea: [Link] for i in range(len(puntos)))/2
# O(N) # Calcula si un punto está dentro de un poligono convexo
def precomputo(N : int, mod : int): # Asume que los puntos están ordenados en sentido horario
fact = [1] * (N+1) # o anti-horario
inv = [1] * (N+1) # O(N)
inv_fact = [1] * (N+1) def PuntoEnPoligono(p,puntos):
for i in range(2,N+1): for i in range(len(puntos)):
fact[i] = (fact[i-1] * i) % mod p_i = puntos[i]
inv[i] = (mod - (mod // i) * inv[mod % i]) % mod p_ip1 = puntos[(i+1)%len(puntos)]
inv_fact[i] = (inv_fact[i-1] * inv[i]) % mod area = producto_cruz(resta_punto(p_i,p),resta_punto(p_ip1,p))
# fact[i] = factorial de i if area<0:
# inv[i] = inverso de i return False
# inv_fact[i] = inverso del factorial de i return True
return fact, inv, inv_fact
Capsula Convexa
Elementos de Geometría 6.7.1 capsula_convexa.py
6.6.1 [Link]
# Calcula la Capsula Convexa de un conjunto
# En esta archivo están las funciones para trabajar # de puntos en el plano
# con puntos/vectores. # La capsula convexa es el mínimo poligono
# Un vector en R^n es una lista de n números reales # convexo que contiene todos los puntos
# Es mínima en, al menos, los siguientes sentidos
Page 11 of 14
Team: –ejemplo–
6.8.1 [Link] return [even[k] + T[k] for k in range(n // 2)] + \
[even[k] - T[k] for k in range(n // 2)]
# Calcular el mínimo entero no negativo excluido
# de un iterable. def ifft(a):
# Importante porque el número de Grundy de un estado # Compute the inverse FFT by taking the FFT of the complex
# de un juego es el MEX de los Grundy de los estados conjugate,
# a los que se puede llegar. # scaling the result, and taking the complex conjugate again.
# O(N) n = len(a)
a_conj = [[Link]() for x in a]
def MEX(iterable): y = fft(a_conj)
n = len(iterable) return [([Link]() / n) for x in y]
esta = [False] * (n+1)
for i in iterable: def convolve(x, y):
if i <= n: # Length of the result after convolution
esta[i] = True n = len(x) + len(y) - 1
(UNLaM)
mex = 0 # Pad x and y with zeros to length n
while mex<n and esta[mex]: x_padded = x + [0] * (n - len(x))
mex += 1 y_padded = y + [0] * (n - len(y))
return mex
# Compute the FFT of both sequences
# Versión más corta pero menos performante
# por utilizar un set fft_x = fft(x_padded)
def MEX_byCopilot(iterable): fft_y = fft(y_padded)
mex = 0 # Point-wise multiplication of the FFTs
conjunto = set(iterable) fft_product = [a * b for a, b in zip(fft_x, fft_y)]
while mex in conjunto:
mex += 1 # Compute the inverse FFT to get the convolution result
return mex result = ifft(fft_product)
# Since the output may have small imaginary parts due to numerical
Identities errors, return the real part
Cn = 2(2n−1)
n+1 Cn−1 return [round([Link]) for r in result]
1 2n
Cn = n+1 n 6.11.2 [Link]
4n√
Cn ∼ n3/2 π
F2n+1 = Fn2 + Fn+1
2 def ntt(a, n, p, g):
2 2 # Aplica la Transformada Número Teórico (NTT) a la secuencia a
P2nn = Fn+1 − Fn−1
F result = a[:]
i=1 Fi = Fn+2 − 1 for length in range(1, n, 2):
Fn+i Fn+j − Fn Fn+i+j = (−1)n Fi Fj w_n = pow(g, (p - 1) // (2 * length), p)
Pn i r n+1 −1 w = 1
i=0 r = r−1 for start in range(0, n, 2 * length):
Pn 2 n·(n+1)·(2n+1) for i in range(length):
i=1 i = 6 u = result[start + i]
Pn 3 n·(n+1) 2 v = (result[start + i + length] * w) % p
i=1 i =
Page 12 of 14
2 result[start + i] = (u + v) % p
Pn 4 n·(n+1)·(2n+1)·(3n2 +3n−1) result[start + i + length] = (u - v) % p
i=1 i = 12
Pn 5 n·(n+1) 2 2n2 +2n−1 w = (w * w_n) % p
· return result
i=1 i = 2 3
Pn n−1 n−1 def intt(a, n, p, g):
= 2
Pi=1
n
i−1
n−1
n−1
# Aplica la Transformada Número Teórico Inversa (INTT)
i=1 i · i−1 = n · 2 n_inv = pow(n, p - 2, p) # Inversa de n módulo p
University: Universidad Nacional de La Matanza, DIIT Team: –ejemplo– (UNLaM)Page 13 of 14
Team: –ejemplo–
# g = 3 # Una raíz primitiva módulo 998244353
# es un palindromo y par[i] es el máximo k tal que
#result = convolve_ntt(a, b, p, g) # S[i-k:i+k) es un palindromo.
#
# Recordar que un palindromo es una cadena que se lee
Strings #
#
#
igual de izquierda a derecha que de derecha a izquierda.
O(n).
Bordes def Manacher(S : str) -> tuple[list[int],list[int]]:
7.1.1 [Link] n = len(S)
par, impar = [0]*n, [0]*n
l, r = 0, -1
# Calcula el array de bordes de un string
# Un borde es un substring propio que es for i in range(n):
# tanto prefijo como sufijo k = 1 if i>r else min(impar[l+r-i],r-i)
(UNLaM)
# bordes[i] = k => s[:k) es el mayor borde de s[:i) while i+k<n and i-k>=0 and S[i+k]==S[i-k]:
# Complejidad: O(n) k+=1
k -= 1
# Notar que podemos obtener las apariciones de un impar[i] = k
# string T en un string S calculando if i+k>r: l, r = i-k, i+k
# bordes(T + "#" + S) y contando las apariciones
l,r = 0, -1
# de T en los bordes
for i in range(n):
def bordes(S : str) -> list[int]: k = 1 if i>r else min(par[l+r-i+1],r-i+1)+1
bordes = [0] * len(S) while i+k<=n and i-k>=0 and S[i+k-1]==S[i-k]:
for i in range(1, len(S)): k+=1
# Invariante: bordes[0:i) ya computados k -= 1
j = bordes[i - 1] par[i] = k
while j > 0 and S[i] != S[j]: if i+k-1>r: l, r = i-k, i+k-1
j = bordes[j - 1] return impar, par
if S[i] == S[j]: #Ejemplo
j += 1 #S = "aabbaacaabbaa"
bordes[i] = j #impar, par = Manacher(S)
return [0] + bordes #print(impar)
# para que coincida con la convención #[0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0]
# bordes("abacaba") #print(par)
# [0, 0, 1, 0, 1, 2, 3, 0] #[0, 1, 0, 3, 0, 1, 0, 0, 1, 0, 3, 0, 1]
Función Z Trie
7.2.1 funcion_z.py 7.4.1 [Link]
# de longitud n tal que z[i] es la longitud # de cuantas veces se ha pasado por ese nodo
# del string más largo que comienza en S[i] # O(|S|) para todas las operaciones
# que es prefijo de S
# Es decir, el Prefijo Común Mayor entre T = [[0, dict()]] # (acumulador, hijos)
# S y S[i:] # Puede modificarse para guardar metadata adicional
# Se puede utilizar para encontrar todas las
# ocurrencias de un string T en S # Agrega la cadena S al trie T
# Calculando z(T + "#" + S) y buscando los def Agregar(T : list[tuple[int,dict[str,int]]], S : str) -> int:
University: Universidad Nacional de La Matanza, DIIT Team: –ejemplo– (UNLaM)Page 14 of 14
Team: –ejemplo–
Primos cercanos a 10n Debugging
9941 9949 9967 9973 10007 10009 10037 10039 10061 • ¿Si n = 0 anda? (similar casos borde tipo n=1,
10067 10069 10079 n=2, etc)
99961 99971 99989 99991 100003 100019 100043 100049
100057 100069 • ¿Si hay puntos alineados anda?
999959 999961 999979 999983 1000003 1000033 1000037 • ¿Si es vacío anda?
1000039 • ¿Si hay multiejes anda?
9999943 9999971 9999973 9999991 10000019 10000079
• ¿Si no tiene aristas anda?
10000103 10000121
99999941 99999959 99999971 99999989 100000007 100000037 • ¿Si tiene ciclos anda?
100000039 100000049 • ¿Si tiene un triángulo anda?
(UNLaM)
999999893 999999929 999999937 1000000007 1000000009 • ¿Los arrays son suficientemente grandes? (siempre
1000000021 1000000033 denle bastante de más por las dudas, pero tampoco
se ceben como para que ya no entre en memoria XD)
Cantidad de primos menores que 10n • ¿Puede dar integer overflow? (SIEMPRE mirar el
π(101 ) = 4 ; π(102 ) = 25 ; π(103 ) = 168 ; π(104 ) = 1229 integer overflow con MUCHO cuidado)
; π(105 ) = 9592 ; π(106 ) = 78.498 ; π(107 ) = 664.579 ;
• ¿Podés dividir por cero en algún caso?
π(108 ) = 5.761.455 ; π(109 ) = 50.847.534 ;
π(1010 ) = 455.052,511 ; π(1011 ) = [Link] ; • ¿Estás memorizando la recursión bien?
π(1012 ) = [Link] • ¿El caso base está bien hecho y se llega siempre?
Divisores • ¿Están bien puestas las cotas iniciales de la bi-
0 nary / inicialización del acumulador máximo/mín-
Cantidad de divisores (σ0 ) para algunos n/¬∃n <
n, σ0 (n0 ) > σ0 (n) imo?
σ0 (60) = 12 ; σ0 (120) = 16 ; σ0 (180) = 18 ; σ0 (240) • ¿Estás inicializando bien antes de cada caso?
= 20 ; σ0 (360) = 24 ; σ0 (720) = 30 ; σ0 (840) = 32 • ¿Le copiaste el input dos veces en el archivo de
; σ0 (1260) = 36 ; σ0 (1680) = 40 ; σ0 (10080) = 72 ; entrada (para ver que de igual y bien las dos ve-
σ0 (15120) = 80 ; σ0 (50400) = 108 ; σ0 (83160) = 128 ; ces)? [No aplica cuando viene solo una instancia
σ0 (110880) = 144 ; σ0 (498960) = 200 ; σ0 (554400) = 216 de input]
; σ0 (1081080) = 256 ; σ0 (1441440) = 288 σ0 (4324320) =
384 ; σ0 (8648640) = 448 • ¿Pasa los ejemplos? [No es joda, Leo se quedo
Suma de divisores (σ1 ) para algunos n/¬∃n0 < n, σ1 (n0 ) > afuera de la mundial por esto]
σ1 (n) ; σ1 (96) = 252 ; σ1 (108) = 280 ; σ1 (120) = 360 Hitos de prueba
Page 14 of 14
; σ1 (144) = 403 ; σ1 (168) = 480 ; σ1 (960) = 3048 ; • 45min todas las columnas de la tabla llena
σ1 (1008) = 3224 ; σ1 (1080) = 3600 ; σ1 (1200) = 3844
; σ1 (4620) = 16128 ; σ1 (4680) = 16380 ; σ1 (5040) = • 2h todos conocen todo
19344 ; σ1 (5760) = 19890 ; σ1 (8820) = 31122 ; σ1 (9240)
• 3h reunión estratégica
= 34560 ; σ1 (10080) = 39312 ; σ1 (10920) = 40320 ;
σ1 (32760) = 131040 ; σ1 (35280) = 137826 ; σ1 (36960) • 4h reunión estratégica