Coeficientes binomiales
Los coeficientes binomiales (binomial coefficients) son el número de formas de seleccionar un conjunto de elementos de elementos distintos sin tener en cuenta el orden de disposición de estos elementos (es decir, el número de conjuntos no ordenados).
Los coeficientes binomiales son también los coeficientes en la expansión de (el llamado teorema del binomio):
Se cree que esta fórmula, así como el triángulo que permite calcular de forma eficiente los coeficientes, fue descubierta por Blaise Pascal en el siglo XVII. No obstante, ya la conocía el matemático chino Yang Hui, que vivió en el siglo XIII. Quizá la descubrió el sabio persa Omar Khayyam. Además, el matemático indio Pingala, que vivió antes, en el siglo III a. C., obtuvo resultados similares. El mérito de Newton es que generalizó esta fórmula para exponentes que no son naturales.
Cálculo
Fórmula analítica para el cálculo:
Esta fórmula se puede deducir fácilmente del problema de las disposiciones ordenadas (número de formas de seleccionar elementos distintos de elementos distintos). Primero, contemos el número de selecciones ordenadas de elementos. Hay formas de seleccionar el primer elemento, formas de seleccionar el segundo, formas de seleccionar el tercero, y así sucesivamente. Como resultado, obtenemos la fórmula del número de disposiciones ordenadas: . Podemos pasar fácilmente a las disposiciones no ordenadas, observando que cada disposición no ordenada corresponde a exactamente disposiciones ordenadas ( es el número de permutaciones posibles de elementos). Obtenemos la fórmula final dividiendo por .
Fórmula de recurrencia (asociada al famoso “triángulo de Pascal”):
Es fácil deducirla usando la fórmula analítica.
Nótese que para se asume que el valor de es cero.
Propiedades
Los coeficientes binomiales tienen muchas propiedades distintas. Aquí están las más simples:
-
Regla de simetría:
-
Extracción de un factor:
-
Suma sobre :
-
Suma sobre :
-
Suma sobre y :
-
Suma de los cuadrados:
-
Suma ponderada:
-
Relación con los números de Fibonacci:
Cálculo
Cálculo directo usando la fórmula analítica
La primera fórmula, la directa, es muy fácil de programar, pero es probable que este método desborde incluso para valores relativamente pequeños de y (incluso si la respuesta cabe por completo en algún tipo de dato, el cálculo de los factoriales intermedios puede provocar desbordamiento). Por lo tanto, este método a menudo solo se puede usar con aritmética de grandes números:
int C(int n, int k) {
int res = 1;
for (int i = n - k + 1; i <= n; ++i)
res *= i;
for (int i = 2; i <= k; ++i)
res /= i;
return res;
}Implementación mejorada
Nótese que en la implementación anterior el numerador y el denominador tienen la misma cantidad de factores (), cada uno de los cuales es mayor o igual que 1. Por lo tanto, podemos reemplazar nuestra fracción por un producto de fracciones, cada una de las cuales es de valor real. Sin embargo, en cada paso, después de multiplicar la respuesta actual por cada una de las fracciones siguientes, la respuesta seguirá siendo entera (esto se sigue de la propiedad de extracción de un factor).
Implementación en C++:
int C(int n, int k) {
double res = 1;
for (int i = 1; i <= k; ++i)
res = res * (n - k + i) / i;
return (int)(res + 0.01);
}Acá casteamos con cuidado el número de punto flotante a un entero, teniendo en cuenta que, debido a los errores acumulados, puede quedar ligeramente por debajo del valor verdadero (por ejemplo, en lugar de ).
Triángulo de Pascal
Usando la relación de recurrencia podemos construir una tabla de coeficientes binomiales (triángulo de Pascal) y tomar el resultado de ella. La ventaja de este método es que los resultados intermedios nunca superan la respuesta y calcular cada nuevo elemento de la tabla requiere solo una suma. La desventaja es la ejecución lenta para y grandes si solo se necesita un único valor y no la tabla completa (porque para calcular habrá que construir una tabla de todos los , o al menos hasta ). La complejidad temporal se puede considerar .
Implementación en C++:
const int maxn = ...;
int C[maxn + 1][maxn + 1];
C[0][0] = 1;
for (int n = 1; 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];
}Si no hace falta la tabla completa de valores, basta con guardar solo las dos últimas filas (la fila -ésima actual y la -ésima anterior).
Cálculo en {data-toc-label=“Cálculo en O(1)”}
Por último, en algunas situaciones conviene precalcular todos los factoriales para producir después cualquier coeficiente binomial necesario con solo dos divisiones. Esto puede ser ventajoso al usar aritmética de grandes números, cuando la memoria no permite precalcular todo el triángulo de Pascal.
Cálculo de coeficientes binomiales módulo {data-toc-label=“Cálculo de coeficientes binomiales módulo m”}
Con bastante frecuencia aparece el problema de calcular coeficientes binomiales módulo algún .
Coeficiente binomial para pequeño {data-toc-label=“Coeficiente binomial para n pequeño”}
El enfoque del triángulo de Pascal discutido antes se puede usar para calcular todos los valores de para razonablemente pequeño, ya que requiere complejidad temporal . Este enfoque puede manejar cualquier módulo, porque solo se usan operaciones de suma.
Coeficiente binomial módulo un primo grande
La fórmula de los coeficientes binomiales es
así que si queremos calcularlo módulo algún primo obtenemos
Primero precalculamos todos los factoriales módulo hasta en tiempo .
factorial[0] = 1;
for (int i = 1; i <= MAXN; i++) {
factorial[i] = factorial[i - 1] * i % m;
}Y después podemos calcular el coeficiente binomial en tiempo .
long long binomial_coefficient(int n, int k) {
return factorial[n] * inverse(factorial[k] * factorial[n - k] % m) % m;
}Incluso podemos calcular el coeficiente binomial en tiempo si precalculamos los inversos de todos los factoriales en usando el método habitual para calcular el inverso, o incluso en tiempo usando la congruencia y el método para calcular todos los inversos en .
long long binomial_coefficient(int n, int k) {
return factorial[n] * inverse_factorial[k] % m * inverse_factorial[n - k] % m;
}Coeficiente binomial módulo una potencia de primo { #mod-prime-pow}
Acá queremos calcular el coeficiente binomial módulo alguna potencia de primo, es decir para algún primo . Si , entonces podemos usar el mismo método descrito en la sección anterior. Pero si , entonces al menos uno de y no es coprimo con , y por lo tanto no podemos calcular los inversos: no existen. No obstante, podemos calcular el coeficiente binomial.
La idea es la siguiente: Para cada calculamos el mayor exponente tal que divide a , es decir . Sea ese número. Y sea . Entonces podemos escribir el coeficiente binomial como:
Lo interesante es que ahora está libre del divisor primo . Por lo tanto es coprimo con , y podemos calcular los inversos modulares de y .
Después de precalcular todos los valores de y , lo que se puede hacer de forma eficiente con programación dinámica en , podemos calcular el coeficiente binomial en tiempo . O precalcular todos los inversos y todas las potencias de , y entonces calcular el coeficiente binomial en .
Nótese que, si , entonces , y el coeficiente binomial es .
Coeficiente binomial módulo un número arbitrario
Ahora calculamos el coeficiente binomial módulo algún módulo arbitrario .
Sea la factorización en primos de igual a . Podemos calcular el coeficiente binomial módulo para cada . Esto nos da congruencias distintas. Como todos los módulos son coprimos, podemos aplicar el Teorema Chino del Resto para calcular el coeficiente binomial módulo el producto de los módulos, que es el coeficiente binomial deseado módulo .
Coeficiente binomial para grande y módulo pequeño {data-toc-label=“Coeficiente binomial para n grande y módulo pequeño”}
Cuando es demasiado grande, los algoritmos discutidos arriba se vuelven impracticables. Sin embargo, si el módulo es pequeño todavía hay formas de calcular .
Cuando el módulo es primo, hay 2 opciones:
- Se puede aplicar el teorema de Lucas , que parte el problema de calcular en problemas de la forma donde . Si cada coeficiente reducido se calcula usando factoriales e inversos factoriales precalculados, la complejidad es .
- Se puede usar el método para calcular el factorial módulo P para obtener los valores de y requeridos y usarlos como se describe en la sección de módulo potencia de primo. Esto toma .
Cuando no es primo pero es libre de cuadrados, se pueden obtener los factores primos de y calcular el coeficiente módulo cada factor primo con cualquiera de los métodos anteriores, y la respuesta global se obtiene con el Teorema Chino del Resto.
Cuando no es libre de cuadrados, se puede aplicar una generalización del teorema de Lucas para potencias de primo en lugar del teorema de Lucas.
Problemas de práctica
- Codechef - Number of ways
- Codeforces - Curious Array
- LightOj - Necklaces
- HACKEREARTH: Binomial Coefficient
- SPOJ - Ada and Teams
- SPOJ - Greedy Walking
- UVa 13214 - The Robot’s Grid
- SPOJ - Good Predictions
- SPOJ - Card Game
- SPOJ - Topper Rama Rao
- UVa 13184 - Counting Edges and Graphs
- Codeforces - Anton and School 2
- Codeforces - Bacterial Melee
- Codeforces - Points, Lines and Ready-made Titles
- SPOJ - The Ultimate Riddle
- CodeChef - Long Sandwich
- Codeforces - Placing Jinas