Skip to Content

Cómo leer fórmulas matemáticas en algoritmos

Muchos programadores sienten rechazo o inseguridad frente a enunciados repletos de símbolos griegos, sumatorias dobles o notación formal.

La realidad es simple: la notación matemática formal es pseudocódigo comprimido. Una vez que aprendés a mapear cada símbolo a su estructura de control equivalente (bucles for, acumuladores, condicionales if y arreglos), leer fórmulas se vuelve tan directo como leer código.


1. Sumatorias (\sum) \longrightarrow Bucles for con Acumulador

El símbolo sigma mayúscula \sum representa una suma acumulada de términos sobre un rango de índices.

Sumatoria simple

i=1nai=a1+a2++an\sum_{i=1}^{n} a_i = a_1 + a_2 + \dots + a_n
// Arreglo 'a' de tamaño n + 1 (1-indexed) long long suma = 0; for (int i = 1; i <= n; i++) { suma += a[i]; }
# Lista 'a' de tamaño n + 1 (1-indexed) suma = sum(a[i] for i in range(1, n + 1))

Sumatoria doble (Bucles anidados)

i=1nj=in(ai+aj)\sum_{i=1}^{n} \sum_{j=i}^{n} (a_i + a_j)
long long total = 0; for (int i = 1; i <= n; i++) { for (int j = i; j <= n; j++) { total += (a[i] + a[j]); } }
total = 0 for i in range(1, n + 1): for j in range(i, n + 1): total += (a[i] + a[j])

Sumatoria condicional (Notación de corchetes de Iverson [P][P])

i=1nai[ai>0]\sum_{i=1}^{n} a_i \cdot [a_i > 0]

El corchete [P][P] vale 11 si la condición PP es verdadera y 00 si es falsa. En código equivale a un condicional if:

long long suma_positivos = 0; for (int i = 1; i <= n; i++) { if (a[i] > 0) { suma_positivos += a[i]; } }
suma_positivos = sum(a[i] for i in range(1, n + 1) if a[i] > 0)

2. Productorias (\prod) \longrightarrow Acumulador Multiplicativo

El símbolo pi mayúscula \prod representa el producto de una secuencia de factores.

i=1ni=1×2××n=n!\prod_{i=1}^{n} i = 1 \times 2 \times \dots \times n = n!
const long long MOD = 1e9 + 7; long long producto = 1; for (int i = 1; i <= n; i++) { producto = (producto * i) % MOD; // Módulo preventivo contra desbordes }
MOD = 10**9 + 7 producto = 1 for i in range(1, n + 1): producto = (producto * i) % MOD

3. Aritmética Modular (,(modm)\equiv, \pmod m) \longrightarrow Operador %

La expresión ab(modm)a \equiv b \pmod m (se lee “a es congruente con b módulo m”) significa que aa y bb dejan el mismo resto al dividirse por mm, o equivalentemente, que (ab)(a - b) es divisible por mm.

Propiedades algebraicas en código

Definimos la constante usual de competencias:

const long long MOD = 1e9 + 7;
  • Suma modular: (a+b)modm=((amodm)+(bmodm))modm(a + b) \bmod m = ((a \bmod m) + (b \bmod m)) \bmod m.
  • Resta modular: (ab)modm=((amodm)(bmodm)+m)modm(a - b) \bmod m = ((a \bmod m) - (b \bmod m) + m) \bmod m. (El +m+ m es crucial en C++ y Java para garantizar que el resultado no sea negativo).
  • Multiplicación modular: (a×b)modm=((amodm)×(bmodm))modm(a \times b) \bmod m = ((a \bmod m) \times (b \bmod m)) \bmod m.

Inverso Modular (a1(modm)a^{-1} \pmod m)

En aritmética modular no existe la división directa. Para calcular ab(modm)\frac{a}{b} \pmod m, multiplicamos por el inverso modular de bb:

a×b1(modm)a \times b^{-1} \pmod m

Por el Pequeño Teorema de Fermat, si mm es un número primo y bb no es múltiplo de mm (gcd(b,m)=1\gcd(b, m) = 1):

bm11(modm)    b1bm2(modm)b^{m-1} \equiv 1 \pmod m \implies b^{-1} \equiv b^{m-2} \pmod m
#include <cassert> const long long MOD = 1e9 + 7; long long power(long long base, long long exp, long long mod = MOD) { long long res = 1; base %= mod; while (exp > 0) { if (exp % 2 == 1) res = (res * base) % mod; base = (base * base) % mod; exp /= 2; } return res; } long long modInverse(long long b, long long mod = MOD) { long long b_mod = ((b % mod) + mod) % mod; assert(b_mod != 0 && "El inverso modular no existe para múltiplos de mod"); return power(b_mod, mod - 2, mod); }
MOD = 10**9 + 7 def power(base: int, exp: int, mod: int = MOD) -> int: return pow(base, exp, mod) def mod_inverse(b: int, mod: int = MOD) -> int: b_mod = b % mod assert b_mod != 0, "El inverso modular no existe para múltiplos de mod" return pow(b_mod, mod - 2, mod)

[!NOTE] En C++, res * base entra en un long long siempre que mod 3109\le 3 \cdot 10^9 (ya que (3109)291018<2631(3 \cdot 10^9)^2 \approx 9 \cdot 10^{18} < 2^{63}-1). Para módulos mayores a 31093 \cdot 10^9 se debe recurrir a __int128. En Python los enteros tienen precisión arbitraria automática.


4. Coeficientes Binomiales ((nk)\binom{n}{k}) \longrightarrow Combinatoria “n en k”

El símbolo (nk)\binom{n}{k} (se lee “n en k” o “número combinatorio”) representa la cantidad de subconjuntos de kk elementos que se pueden formar a partir de un conjunto de nn elementos:

(nk)=n!k!(nk)!\binom{n}{k} = \frac{n!}{k!(n-k)!}

Identidad de Pascal (Base de Programación Dinámica)

(nk)=(n1k1)+(n1k)\binom{n}{k} = \binom{n-1}{k-1} + \binom{n-1}{k}
const int MAXN = 1000; const long long MOD = 1e9 + 7; // Tabla de (MAXN + 1) x (MAXN + 1): ocupa ~8 MB de memoria long long C[MAXN + 1][MAXN + 1]; void build_pascal() { for (int n = 0; n <= MAXN; n++) { C[n][0] = C[n][n] = 1; for (int k = 1; k < n; k++) { C[n][k] = (C[n - 1][k - 1] + C[n - 1][k]) % MOD; } } }
MAXN = 1000 MOD = 10**9 + 7 C = [[0] * (MAXN + 1) for _ in range(MAXN + 1)] def build_pascal(): for n in range(MAXN + 1): C[n][0] = C[n][n] = 1 for k in range(1, n): C[n][k] = (C[n - 1][k - 1] + C[n - 1][k]) % MOD

5. Funciones Piso (x\lfloor x \rfloor) y Techo (x\lceil x \rceil) \longrightarrow División Entera

  • Piso (x\lfloor x \rfloor): Redondea hacia abajo al entero menor o igual (3.8=3\lfloor 3.8 \rfloor = 3, 2.2=3\lfloor -2.2 \rfloor = -3).
  • Techo (x\lceil x \rceil): Redondea hacia arriba al entero mayor o igual (3.1=4\lceil 3.1 \rceil = 4).

Cálculo de Techo sin usar Punto Flotante

Para a0a \ge 0 y b>0b > 0:

  1. Fórmula estándar:

    ab=a+b1b\left\lceil \frac{a}{b} \right\rceil = \left\lfloor \frac{a + b - 1}{b} \right\rfloor

    En código: (a + b - 1) / b

  2. Alternativa segura contra desborde: Si aa puede estar cerca del valor máximo de long long (LLONG_MAX), sumar b1b - 1 causaría un integer overflow. En ese caso, usá:

    ab=ab+[amodb0]\left\lceil \frac{a}{b} \right\rceil = \left\lfloor \frac{a}{b} \right\rfloor + [a \bmod b \neq 0]

    En código: a / b + (a % b != 0)


6. Notación Asintótica (O,Ω,Θ\mathcal{O}, \Omega, \Theta) vs Escenarios de Entrada

Ejemplo con Quicksort clásico:

  • El peor caso de Quicksort es Θ(n2)\Theta(n^2) (y por ende también O(n2)\mathcal{O}(n^2)).
  • El mejor caso de Quicksort es Θ(nlogn)\Theta(n \log n) (y por ende también Ω(nlogn)\Omega(n \log n)).
  • El caso promedio es Θ(nlogn)\Theta(n \log n).

Tabla de referencia: Complejidad vs NN para 1 Segundo (10810^8 operaciones)

NN MáximoComplejidad RequeridaAlgoritmos Típicos
N10N \le 10O(N!)\mathcal{O}(N!)Búsqueda exhaustiva sobre permutaciones (std::next_permutation).
N20N \le 20O(2N)\mathcal{O}(2^N)Búsqueda sobre todos los subconjuntos, DP con máscaras de bits (Bitmask DP).
N500N \le 500O(N3)\mathcal{O}(N^3)Algoritmo de Floyd-Warshall, multiplicación clásica de matrices.
N5000N \le 5\,000O(N2)\mathcal{O}(N^2)Programación Dinámica 2D, ordenamientos simples, bucles anidados.
N2105N \le 2 \cdot 10^5O(NlogN)\mathcal{O}(N \log N)Ordenamientos eficientes (MergeSort), Segment Tree, Búsqueda Binaria, Dijkstra.
N107N \le 10^7O(N)\mathcal{O}(N)Dos Punteros, Sumas de Prefijos, Criba de Eratóstenes.
N1018N \le 10^{18}O(logN)\mathcal{O}(\log N) o O(1)\mathcal{O}(1)Exponenciación binaria, MCD de Euclides, fórmulas algebraicas cerradas.

7. Conjuntos, Lógica Formal y Operadores Bitwise

Pertenencia y Cuantificadores

  • xSx \in S: xx pertenece al conjunto SS (S.contains(x) en C++20, S.count(x) > 0 en C++ clásico o x in S en Python).
  • ABA \subseteq B: AA es subconjunto de BB.
  • ABA \cup B: Unión de conjuntos.
  • ABA \cap B: Intersección de conjuntos.
  • ABA \setminus B: Diferencia de conjuntos (elementos en AA que no están en BB).
  • xS\forall x \in S: “Para todo elemento xx de SS (bucle donde todos deben cumplir la condición).
  • xS\exists x \in S: “Existe al menos un elemento xx en SS (bucle con interrupción al hallar el primero).
  • {xSP(x)}\{ x \in S \mid P(x) \}: “El conjunto de elementos xx de SS que cumplen el predicado P(x)P(x).

Operadores Lógicos vs Operadores Bitwise

OperaciónSímbolo MatemáticoOperador Lógico (Booleano)Operador Bit a Bit (Bitwise)
Negación / NOT¬P\neg P!a (da true o false)~a (invierte bits; en complemento a dos da ~a == -a - 1)
Conjunción / ANDPQP \land Qa && ba & b
Disyunción / ORPQP \lor Qa || ba | b
OR Exclusivo / XORPQP \oplus Qa != b (para booleanos)a ^ b

8. Recurrencias de Programación Dinámica

Las fórmulas de DP definen el valor de un estado a partir de subproblemas más pequeños.

Ejemplo: Problema de la Mochila 0/1

dp[i][w]={dp[i1][w]si w<pesoimax(dp[i1][w],  dp[i1][wpesoi]+valori)si wpesoidp[i][w] = \begin{cases} dp[i-1][w] & \text{si } w < \text{peso}_i \\ \max\Big(dp[i-1][w], \; dp[i-1][w - \text{peso}_i] + \text{valor}_i\Big) & \text{si } w \ge \text{peso}_i \end{cases}

Desglose paso a paso:

  1. Dimensiones del estado: dp[i][w]dp[i][w] es la ganancia máxima considerando los primeros ii objetos con capacidad restante ww.
  2. Caso 1 (w<pesoiw < \text{peso}_i): El objeto ii es demasiado pesado para la capacidad actual; obligatoriamente se ignora (dp[i1][w]dp[i-1][w]).
  3. Caso 2 (wpesoiw \ge \text{peso}_i): Podemos elegir entre no tomarlo (dp[i1][w]dp[i-1][w]) o tomarlo (dp[i1][wpesoi]+valoridp[i-1][w - \text{peso}_i] + \text{valor}_i). La función max\max selecciona la opción más rentable.
// peso y valor están 1-indexed (tamaño n + 1) vector<vector<long long>> dp(n + 1, vector<long long>(W + 1, 0)); for (int i = 1; i <= n; i++) { for (int w = 0; w <= W; w++) { if (w < peso[i]) { dp[i][w] = dp[i - 1][w]; } else { dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - peso[i]] + valor[i]); } } }
# peso y valor están 1-indexed (tamaño n + 1) dp = [[0] * (W + 1) for _ in range(n + 1)] for i in range(1, n + 1): for w in range(W + 1): if w < peso[i]: dp[i][w] = dp[i - 1][w] else: dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - peso[i]] + valor[i])

9. Álgebra de Bits en Fórmulas

  • 2k2^k: Potencia de 2     \implies Corrimiento de bits a la izquierda: 1LL << k en C++ o 1 << k en Python.
  • Representación binaria como sumatoria: x=b=0B12bbitb(x)x = \sum_{b=0}^{B-1} 2^b \cdot \text{bit}_b(x)
  • Cantidad de bits encendidos (popcount): bbitb(x)\sum_{b} \text{bit}_b(x)
    • En C++20: std::popcount(static_cast<unsigned long long>(x)) (incluyendo <bit>).
    • En C++ clásico (GCC/Clang): __builtin_popcountll(x).
    • En Python: x.bit_count() (en Python 3.10+) o bin(x).count("1") (versiones anteriores).

10. Método en 4 Pasos para Programar Cualquier Fórmula

  1. Paso 1: Tipos de datos e indexación: Verificá rangos numéricos. Si los resultados intermedios exceden 21092 \cdot 10^9, utilizá long long. Declarar arreglos con tamaño n+1n + 1 para mantener coincidencia con los índices de la fórmula.
  2. Paso 2: Localizar el caso base: ¿Qué valor toma la expresión cuando el índice vale 00 o 11? Esos valores inicializan tus arreglos o variables acumuladoras.
  3. Paso 3: Estructura de bucles: Mapeá cada sumatoria o cuantificador exterior a un bucle for, y colocá la expresión central en el cuerpo más interno.
  4. Paso 4: Factorización y optimización: Buscá si la fórmula permite separar variables independientes para reducir la complejidad temporal. Por ejemplo: i=1nj=1naibj=(i=1nai)(j=1nbj)\sum_{i=1}^n \sum_{j=1}^n a_i \cdot b_j = \left( \sum_{i=1}^n a_i \right) \cdot \left( \sum_{j=1}^n b_j \right) Transforma un algoritmo de O(N2)\mathcal{O}(N^2) a O(N)\mathcal{O}(N) precalculando sumas parciales.