Problem Summary

For each integer \(n\) with \(1 \le n \le 100\), and each \(r\) with \(0 \le r \le n\), consider the binomial coefficient \(\binom{n}{r}\). The task is to count how many pairs \((n,r)\) satisfy \(\binom{n}{r} \gt 10^6\).

The key point is that we do not need to evaluate every coefficient with factorials. The implementations sweep through each row from left to right, stop as soon as the row crosses one million, and count the rest of that row from symmetry.

Mathematical Approach

Let \(T=10^6\). The useful structure comes from comparing consecutive binomial coefficients in the same row.

Generating a row without factorials

The definition $$\binom{n}{r}=\frac{n!}{r!(n-r)!}$$ is mathematically correct, but it is not the most convenient way to compute many values in one row. If we divide consecutive coefficients, we get $$\frac{\binom{n}{r}}{\binom{n}{r-1}}=\frac{n-r+1}{r},$$ so $$\binom{n}{r}=\binom{n}{r-1}\frac{n-r+1}{r}.$$ Starting from \(\binom{n}{0}=1\), this recurrence generates the row one entry at a time.

This is exactly the invariant used in the code: after the update for a given \(r\), the running value is the exact integer \(\binom{n}{r}\). No Pascal triangle table and no factorial array are required.

Why the left half is enough

Two standard facts control the whole row: $$\binom{n}{r}=\binom{n}{n-r}$$ and $$\frac{\binom{n}{r}}{\binom{n}{r-1}}=\frac{n-r+1}{r}.$$ For every \(1 \le r \le \lfloor n/2 \rfloor\), that ratio is greater than 1, so the coefficients strictly increase as we move from the left edge toward the middle. After the midpoint, symmetry forces the same values to reappear in reverse order.

Therefore the first coefficient above the threshold in the left half completely determines every coefficient above the threshold in that row.

Turning the first crossing into a row count

Suppose \(r_0\) is the smallest index such that $$\binom{n}{r_0} \gt T.$$ By monotonicity up to the midpoint and symmetry afterward, every index $$r_0 \le r \le n-r_0$$ also satisfies \(\binom{n}{r} \gt T\). The number of such indices is $$ (n-r_0)-r_0+1 = n+1-2r_0.$$ This is the row contribution added by the implementations as soon as the first crossing is found.

If the scan reaches \(\lfloor n/2 \rfloor\) without exceeding \(T\), then the entire row stays at or below the threshold and contributes 0.

Worked Example: \(n=23\)

For \(n=23\), the recurrence produces $$\binom{23}{1}=23,\ \binom{23}{2}=253,\ \binom{23}{3}=1771,\ \binom{23}{4}=8855,\ \binom{23}{5}=33649,$$ $$\binom{23}{6}=100947,\ \binom{23}{7}=245157,\ \binom{23}{8}=490314,\ \binom{23}{9}=817190,\ \binom{23}{10}=1144066.$$ The first value above one million appears at \(r_0=10\). Symmetry then gives the matching values at \(r=11,12,13\), so the exceeding block is $$r=10,11,12,13.$$ Its size is $$23+1-2\cdot 10=4.$$ This is the same reasoning used for every row from 1 through 100.

How the Code Works

Inputs and checkpoint

The C++, Python, and Java implementations all work with two mathematical parameters: an upper row bound \(n_{\max}\) and a threshold \(T\). For the Project Euler problem they default to \(n_{\max}=100\) and \(T=10^6\). Each implementation also contains a small checkpoint: when \(n_{\max}=10\) and \(T=10\), the correct count is 25.

Single-value sweep across each row

For a fixed \(n\), the implementation starts with the coefficient \(1\) at \(r=0\). It then advances through $$r=1,2,\dots,\left\lfloor \frac{n}{2}\right\rfloor,$$ updating the running coefficient by $$c \leftarrow c\frac{n-r+1}{r}.$$ Because the update always lands on the exact next binomial coefficient, the loop never needs floating-point arithmetic or a precomputed table.

Early termination by symmetry

As soon as the running value exceeds the threshold, the implementation adds $$n+1-2r$$ to the total and immediately stops processing that row. This is correct because the first crossing identifies the entire central block above the threshold. For Problem 53, this also keeps the intermediate values modest: the computation breaks right after the first value above one million, so ordinary integer arithmetic is sufficient in all three languages.

Complexity Analysis

With upper bound \(N\), the loop performs at most $$\sum_{n=1}^{N}\left\lfloor \frac{n}{2}\right\rfloor = O(N^2)$$ coefficient updates. For \(N=100\), that worst-case total is only 2500 updates, and the real total is smaller because many rows stop before the midpoint.

The memory usage is \(O(1)\): the implementation stores only the current coefficient, the running answer, and a few counters and parameters. That matches the lightweight structure of the actual solution.

Footnotes and References

  1. Problem statement: Project Euler 53 - Combinatoric selections
  2. Binomial coefficient: Wikipedia - Binomial coefficient
  3. Pascal's triangle: Wikipedia - Pascal's triangle
  4. Combination: Wikipedia - Combination

Problemzusammenfassung

Für jedes ganze \(n\) mit \(1 \le n \le 100\) und jedes \(r\) mit \(0 \le r \le n\) betrachten wir den Binomialkoeffizienten \(\binom{n}{r}\). Gesucht ist die Anzahl der Paare \((n,r)\), für die \(\binom{n}{r} \gt 10^6\) gilt.

Der entscheidende Punkt ist, dass man nicht jeden Wert mit Fakultäten ausrechnen muss. Die Implementierungen laufen jede Zeile von links nach rechts ab, stoppen beim ersten Überschreiten von einer Million und zählen den Rest der Zeile dann über die Symmetrie.

Mathematischer Ansatz

Setze \(T=10^6\). Die nützliche Struktur entsteht aus dem Verhältnis aufeinanderfolgender Binomialkoeffizienten innerhalb derselben Zeile.

Eine Zeile ohne Fakultäten erzeugen

Die Definition $$\binom{n}{r}=\frac{n!}{r!(n-r)!}$$ ist mathematisch korrekt, aber nicht die praktischste Form für viele Werte derselben Zeile. Teilt man zwei benachbarte Koeffizienten, erhält man $$\frac{\binom{n}{r}}{\binom{n}{r-1}}=\frac{n-r+1}{r},$$ also $$\binom{n}{r}=\binom{n}{r-1}\frac{n-r+1}{r}.$$ Ausgehend von \(\binom{n}{0}=1\) lässt sich die gesamte linke Zeilenhälfte damit Schritt für Schritt aufbauen.

Genau das ist die Invariante im Code: Nach dem Update für ein bestimmtes \(r\) ist der laufende Wert exakt \(\binom{n}{r}\). Weder ein komplettes Pascalsches Dreieck noch ein Fakultätsarray werden benötigt.

Warum die linke Hälfte genügt

Zwei bekannte Tatsachen kontrollieren die ganze Zeile: $$\binom{n}{r}=\binom{n}{n-r}$$ und $$\frac{\binom{n}{r}}{\binom{n}{r-1}}=\frac{n-r+1}{r}.$$ Für jedes \(1 \le r \le \lfloor n/2 \rfloor\) ist dieses Verhältnis größer als 1, also steigen die Koeffizienten streng an, solange wir uns vom linken Rand zur Mitte bewegen. Nach dem Mittelpunkt erscheinen dieselben Werte wegen der Symmetrie in umgekehrter Reihenfolge.

Deshalb bestimmt der erste Wert über dem Schwellwert in der linken Hälfte sofort alle weiteren Werte derselben Zeile, die ebenfalls über dem Schwellwert liegen.

Aus dem ersten Überschreiten die Zeilenanzahl gewinnen

Sei \(r_0\) der kleinste Index mit $$\binom{n}{r_0} \gt T.$$ Wegen der Monotonie bis zur Mitte und der Symmetrie danach gilt dann auch $$\binom{n}{r} \gt T \quad \text{für alle } r_0 \le r \le n-r_0.$$ Die Anzahl dieser Indizes ist $$ (n-r_0)-r_0+1 = n+1-2r_0.$$ Genau dieser Ausdruck wird von den Implementierungen addiert, sobald das erste Überschreiten gefunden wurde.

Falls die Suche bis \(\lfloor n/2 \rfloor\) reicht, ohne \(T\) zu übertreffen, trägt die gesamte Zeile 0 bei.

Durchgerechnetes Beispiel: \(n=23\)

Für \(n=23\) liefert die Rekurrenz $$\binom{23}{1}=23,\ \binom{23}{2}=253,\ \binom{23}{3}=1771,\ \binom{23}{4}=8855,\ \binom{23}{5}=33649,$$ $$\binom{23}{6}=100947,\ \binom{23}{7}=245157,\ \binom{23}{8}=490314,\ \binom{23}{9}=817190,\ \binom{23}{10}=1144066.$$ Der erste Wert über einer Million erscheint also bei \(r_0=10\). Durch Symmetrie kommen die Spiegelwerte bei \(r=11,12,13\) dazu, also ist der Block der gültigen Indizes $$r=10,11,12,13.$$ Seine Größe ist $$23+1-2\cdot 10=4.$$ Genau so wird jede Zeile von 1 bis 100 behandelt.

Wie der Code arbeitet

Parameter und Kontrolltest

Die C++-, Python- und Java-Implementierungen arbeiten mit zwei mathematischen Parametern: einer oberen Zeilengrenze \(n_{\max}\) und einem Schwellwert \(T\). Für die Project-Euler-Aufgabe sind die Standardwerte \(n_{\max}=100\) und \(T=10^6\). Zusätzlich enthält jede Implementierung einen kleinen Testfall: Für \(n_{\max}=10\) und \(T=10\) muss das Ergebnis 25 sein.

Zeilenscan mit nur einem laufenden Koeffizienten

Für festes \(n\) startet die Implementierung bei \(r=0\) mit dem Koeffizienten \(1\). Danach läuft sie durch $$r=1,2,\dots,\left\lfloor \frac{n}{2}\right\rfloor$$ und aktualisiert den laufenden Koeffizienten mit $$c \leftarrow c\frac{n-r+1}{r}.$$ Weil dieses Update immer exakt den nächsten Binomialkoeffizienten liefert, braucht die Schleife weder Gleitkommaarithmetik noch eine vorgerechnete Tabelle.

Frühes Abbrechen dank Symmetrie

Sobald der laufende Wert den Schwellwert überschreitet, addiert die Implementierung $$n+1-2r$$ zur Gesamtsumme und beendet die Bearbeitung dieser Zeile sofort. Das ist korrekt, weil das erste Überschreiten den gesamten mittleren Block oberhalb des Schwellwerts festlegt. Für Problem 53 hält diese Strategie auch die Zwischenwerte klein: Direkt nach dem ersten Wert über einer Million wird abgebrochen, sodass gewöhnliche Ganzzahlarithmetik in allen drei Sprachen ausreicht.

Komplexitätsanalyse

Bei einer Obergrenze \(N\) werden höchstens $$\sum_{n=1}^{N}\left\lfloor \frac{n}{2}\right\rfloor = O(N^2)$$ Koeffizientenaktualisierungen durchgeführt. Für \(N=100\) sind das im schlimmsten Fall nur 2500 Updates, und tatsächlich noch weniger, weil viele Zeilen vor der Mitte enden.

Der Speicherverbrauch ist \(O(1)\): Gespeichert werden nur der aktuelle Koeffizient, die laufende Antwort sowie einige Zähler und Parameter. Das entspricht der schlanken Struktur der tatsächlichen Lösung.

Fußnoten und Referenzen

  1. Problemseite: Project Euler 53 - Combinatoric selections
  2. Binomialkoeffizient: Wikipedia - Binomial coefficient
  3. Pascalsches Dreieck: Wikipedia - Pascal's triangle
  4. Kombination: Wikipedia - Combination

Problem Özeti

\(1 \le n \le 100\) ve \(0 \le r \le n\) için \(\binom{n}{r}\) binom katsayılarını ele alıyoruz. Amaç, \(\binom{n}{r} \gt 10^6\) koşulunu sağlayan \((n,r)\) çiftlerinin sayısını bulmaktır.

Asıl fikir, her değeri faktöriyel üzerinden ayrı ayrı hesaplamamaktır. Uygulamalar her satırı soldan sağa tarar, bir milyon eşiği ilk kez aşıldığında durur ve satırın geri kalan katkısını simetriden çıkarır.

Matematiksel Yaklaşım

\(T=10^6\) olsun. İşe yarayan yapı, aynı satırdaki ardışık binom katsayıları arasındaki orandır.

Faktöriyel kullanmadan satırı üretmek

Tanım olarak $$\binom{n}{r}=\frac{n!}{r!(n-r)!}$$ yazılır; fakat aynı satırdaki birçok değeri hesaplamak için daha verimli ifade $$\frac{\binom{n}{r}}{\binom{n}{r-1}}=\frac{n-r+1}{r}$$ oranıdır. Buradan $$\binom{n}{r}=\binom{n}{r-1}\frac{n-r+1}{r}$$ elde edilir. Başlangıç değeri \(\binom{n}{0}=1\) olduğundan, satır soldan sağa tek bir değişkenle üretilebilir.

Koddaki temel değişmez de budur: belirli bir \(r\) adımından sonra eldeki yürüyen değer tam olarak \(\binom{n}{r}\) olur. Böylece ne tam bir Pascal üçgeni ne de faktöriyel dizisi gerekir.

Neden yalnızca sol yarıyı taramak yeterlidir

Bütün satırı belirleyen iki gerçek vardır: $$\binom{n}{r}=\binom{n}{n-r}$$ ve $$\frac{\binom{n}{r}}{\binom{n}{r-1}}=\frac{n-r+1}{r}.$$ \(1 \le r \le \lfloor n/2 \rfloor\) için bu oran 1'den büyüktür; yani katsayılar sol uçtan merkeze kadar sıkı biçimde artar. Orta noktadan sonra ise aynı değerler simetri nedeniyle ters sırayla tekrar eder.

Bu yüzden sol yarıda eşiği aşan ilk terimi bulmak, o satırdaki bütün büyük terimlerin yerini tek seferde belirler.

İlk aşımı satır sayısına çevirmek

\(r_0\), şu koşulu sağlayan en küçük indis olsun: $$\binom{n}{r_0} \gt T.$$ Merkeze kadar artış ve sonrasındaki simetri nedeniyle $$r_0 \le r \le n-r_0$$ aralığındaki bütün indisler için de \(\binom{n}{r} \gt T\) olur. Bu indislerin sayısı $$ (n-r_0)-r_0+1 = n+1-2r_0$$ değeridir. Uygulamalar, ilk aşımı görür görmez tam olarak bu miktarı toplama ekler.

Eğer tarama \(\lfloor n/2 \rfloor\) noktasına kadar gidip de eşiği aşmazsa, o satırın katkısı 0'dır.

Çalışılmış Örnek: \(n=23\)

\(n=23\) için yineleme şu değerleri verir: $$\binom{23}{1}=23,\ \binom{23}{2}=253,\ \binom{23}{3}=1771,\ \binom{23}{4}=8855,\ \binom{23}{5}=33649,$$ $$\binom{23}{6}=100947,\ \binom{23}{7}=245157,\ \binom{23}{8}=490314,\ \binom{23}{9}=817190,\ \binom{23}{10}=1144066.$$ Bir milyonu aşan ilk değer \(r_0=10\) konumunda gelir. Simetri yüzünden eşleşen değerler \(r=11,12,13\) konumlarındadır; dolayısıyla eşiği aşan blok $$r=10,11,12,13$$ olur. Blok uzunluğu da $$23+1-2\cdot 10=4$$ eder. 1 ile 100 arasındaki tüm satırlarda yapılan mantık tam olarak budur.

Kod Nasıl Çalışır

Parametreler ve kontrol testi

C++, Python ve Java uygulamalarının hepsi iki matematiksel parametre kullanır: üst satır sınırı \(n_{\max}\) ve eşik \(T\). Project Euler görevi için varsayılan değerler \(n_{\max}=100\) ve \(T=10^6\) olur. Ayrıca her uygulamada küçük bir doğrulama testi vardır: \(n_{\max}=10\) ve \(T=10\) için doğru sonuç 25'tir.

Tek yürüyen katsayıyla satır taraması

Sabit bir \(n\) için uygulama, \(r=0\) noktasında katsayıyı \(1\) alarak başlar. Sonra $$r=1,2,\dots,\left\lfloor \frac{n}{2}\right\rfloor$$ boyunca ilerler ve yürüyen katsayıyı $$c \leftarrow c\frac{n-r+1}{r}$$ güncellemesiyle hesaplar. Bu güncelleme her adımda tam olarak bir sonraki binom katsayısını verdiği için kayan noktalı aritmetiğe ya da önceden kurulmuş tabloya ihtiyaç yoktur.

Simetri sayesinde erken durma

Yürüyen değer eşiği aşar aşmaz uygulama $$n+1-2r$$ miktarını toplama ekler ve o satırı anında bitirir. Bunun doğru olmasının sebebi, ilk aşımın orta bölgedeki tüm büyük terimleri zaten belirlemesidir. Problem 53 için bu yaklaşım ara değerleri de küçük tutar: bir milyonun üstündeki ilk değer görülür görülmez döngü bittiği için üç dilde de sıradan tam sayı aritmetiği yeterlidir.

Karmaşıklık Analizi

Üst sınır \(N\) ise en fazla $$\sum_{n=1}^{N}\left\lfloor \frac{n}{2}\right\rfloor = O(N^2)$$ adet katsayı güncellemesi yapılır. \(N=100\) için bu en kötü durumda yalnızca 2500 güncellemedir; pratikte ise birçok satır orta noktaya gelmeden durduğu için sayı daha da küçüktür.

Bellek kullanımı \(O(1)\)'dir. Uygulama yalnızca güncel katsayıyı, biriken cevabı ve birkaç sayaç ile parametreyi tutar. Bu da gerçek çözümün sade yapısıyla tam uyumludur.

Dipnotlar ve Kaynaklar

  1. Problem sayfası: Project Euler 53 - Combinatoric selections
  2. Binom katsayısı: Wikipedia - Binomial coefficient
  3. Pascal üçgeni: Wikipedia - Pascal's triangle
  4. Kombinasyon: Wikipedia - Combination

Resumen del Problema

Para cada entero \(n\) con \(1 \le n \le 100\), y para cada \(r\) con \(0 \le r \le n\), consideramos el coeficiente binomial \(\binom{n}{r}\). El objetivo es contar cuántos pares \((n,r)\) cumplen \(\binom{n}{r} \gt 10^6\).

La idea decisiva es que no hace falta calcular todos los valores con factoriales. Las implementaciones recorren cada fila de izquierda a derecha, se detienen en cuanto la fila supera un millón y cuentan el resto de la fila usando la simetría.

Enfoque Matemático

Sea \(T=10^6\). La estructura útil aparece al comparar coeficientes binomiales consecutivos dentro de una misma fila.

Generar una fila sin usar factoriales

La definición $$\binom{n}{r}=\frac{n!}{r!(n-r)!}$$ es correcta, pero no es la forma más cómoda de producir muchos valores en la misma fila. Si dividimos dos coeficientes consecutivos, obtenemos $$\frac{\binom{n}{r}}{\binom{n}{r-1}}=\frac{n-r+1}{r},$$ y por tanto $$\binom{n}{r}=\binom{n}{r-1}\frac{n-r+1}{r}.$$ Empezando desde \(\binom{n}{0}=1\), esta recurrencia genera la fila de izquierda a derecha.

Esa es exactamente la invariante utilizada por el código: después de actualizar para un valor de \(r\), el acumulador contiene el entero exacto \(\binom{n}{r}\). No hace falta construir el triángulo de Pascal completo ni almacenar factoriales.

Por qué basta con la mitad izquierda

Dos hechos clásicos controlan toda la fila: $$\binom{n}{r}=\binom{n}{n-r}$$ y $$\frac{\binom{n}{r}}{\binom{n}{r-1}}=\frac{n-r+1}{r}.$$ Para todo \(1 \le r \le \lfloor n/2 \rfloor\), ese cociente es mayor que 1, así que los coeficientes crecen estrictamente desde el borde izquierdo hasta el centro. Después del punto medio, la simetría obliga a repetir los mismos valores en orden inverso.

Por eso, el primer coeficiente que supera el umbral en la mitad izquierda determina de inmediato todos los coeficientes de esa fila que también superan el umbral.

Convertir el primer cruce en una cuenta de fila

Sea \(r_0\) el menor índice tal que $$\binom{n}{r_0} \gt T.$$ Por la monotonía hasta el centro y la simetría después, también se cumple $$\binom{n}{r} \gt T \quad \text{para todo } r_0 \le r \le n-r_0.$$ El número de índices de ese bloque es $$ (n-r_0)-r_0+1 = n+1-2r_0.$$ Esa es exactamente la cantidad que las implementaciones añaden cuando detectan el primer cruce.

Si la exploración llega hasta \(\lfloor n/2 \rfloor\) sin superar \(T\), entonces toda la fila permanece en o por debajo del umbral y aporta 0.

Ejemplo trabajado: \(n=23\)

Para \(n=23\), la recurrencia produce $$\binom{23}{1}=23,\ \binom{23}{2}=253,\ \binom{23}{3}=1771,\ \binom{23}{4}=8855,\ \binom{23}{5}=33649,$$ $$\binom{23}{6}=100947,\ \binom{23}{7}=245157,\ \binom{23}{8}=490314,\ \binom{23}{9}=817190,\ \binom{23}{10}=1144066.$$ El primer valor por encima de un millón aparece en \(r_0=10\). La simetría añade sus reflejos en \(r=11,12,13\), de modo que el bloque que excede el umbral es $$r=10,11,12,13.$$ Su tamaño es $$23+1-2\cdot 10=4.$$ Ese mismo razonamiento se aplica a cada fila entre 1 y 100.

Cómo Funciona el Código

Parámetros y comprobación

Las implementaciones en C++, Python y Java trabajan con dos parámetros matemáticos: una cota superior \(n_{\max}\) y un umbral \(T\). Para el problema de Project Euler usan por defecto \(n_{\max}=100\) y \(T=10^6\). Además, todas incluyen una comprobación pequeña: cuando \(n_{\max}=10\) y \(T=10\), la respuesta correcta es 25.

Recorrido de cada fila con un solo valor

Para un \(n\) fijo, la implementación comienza con el coeficiente \(1\) en \(r=0\). Después avanza por $$r=1,2,\dots,\left\lfloor \frac{n}{2}\right\rfloor$$ y actualiza el valor acumulado mediante $$c \leftarrow c\frac{n-r+1}{r}.$$ Como esa operación produce siempre el siguiente coeficiente binomial exacto, no se necesitan números en coma flotante ni una tabla precomputada.

Salida anticipada por simetría

En cuanto el valor acumulado supera el umbral, la implementación añade $$n+1-2r$$ al total y termina inmediatamente esa fila. Esto es correcto porque el primer cruce identifica todo el bloque central que está por encima del umbral. Para el Problema 53, además, los valores intermedios siguen siendo pequeños: el bucle se corta justo después del primer valor mayor que un millón, así que la aritmética entera ordinaria basta en los tres lenguajes.

Análisis de Complejidad

Con límite superior \(N\), el algoritmo realiza como máximo $$\sum_{n=1}^{N}\left\lfloor \frac{n}{2}\right\rfloor = O(N^2)$$ actualizaciones de coeficientes. Para \(N=100\), ese máximo es solo 2500, y en la práctica es menor porque muchas filas se detienen antes de alcanzar el centro.

El uso de memoria es \(O(1)\): la implementación solo guarda el coeficiente actual, la respuesta acumulada y unos pocos contadores y parámetros. Eso refleja la estructura compacta de la solución real.

Notas y Referencias

  1. Enunciado del problema: Project Euler 53 - Combinatoric selections
  2. Coeficiente binomial: Wikipedia - Binomial coefficient
  3. Triángulo de Pascal: Wikipedia - Pascal's triangle
  4. Combinación: Wikipedia - Combination

问题概述

对每个满足 \(1 \le n \le 100\) 的整数 \(n\),以及每个满足 \(0 \le r \le n\) 的整数 \(r\),考虑二项式系数 \(\binom{n}{r}\)。题目要求统计满足 \(\binom{n}{r} \gt 10^6\) 的所有 \((n,r)\) 对。

真正重要的地方在于,不必用阶乘把每个系数都单独算出来。实现采用的是逐行扫描:从每一行的左端开始向中间推进,一旦第一次超过一百万,就利用对称性直接算出这一整行剩余的贡献。

数学方法

记阈值为 \(T=10^6\)。这个问题最有用的结构,是同一行中相邻二项式系数之间的关系。

不用阶乘逐项生成一行

二项式系数的定义是 $$\binom{n}{r}=\frac{n!}{r!(n-r)!}.$$ 但如果要在固定的 \(n\) 下连续计算很多个 \(r\),更方便的是比较相邻两项: $$\frac{\binom{n}{r}}{\binom{n}{r-1}}=\frac{n-r+1}{r},$$ 因而 $$\binom{n}{r}=\binom{n}{r-1}\frac{n-r+1}{r}.$$ 从 \(\binom{n}{0}=1\) 出发,就能把这一行从左到右逐项生成出来。

代码依赖的核心不变量正是这一点:处理到某个 \(r\) 时,手里的当前值始终精确等于 \(\binom{n}{r}\)。因此既不需要完整存储帕斯卡三角形,也不需要预先准备阶乘数组。

为什么只看左半边就够了

整行的结构由两个事实决定: $$\binom{n}{r}=\binom{n}{n-r}$$ 以及 $$\frac{\binom{n}{r}}{\binom{n}{r-1}}=\frac{n-r+1}{r}.$$ 对所有 \(1 \le r \le \lfloor n/2 \rfloor\),这个比值都大于 1,所以系数从左边界走向中点时会严格递增。过了中点以后,由于对称性,相同的数值会按相反顺序重新出现。

因此,只要在左半边找到第一个超过阈值的位置,就已经知道这一行中所有超过阈值的项落在什么区间里。

由第一次越界直接得到整行计数

设 \(r_0\) 是满足 $$\binom{n}{r_0} \gt T$$ 的最小下标。由于到中点之前单调递增,而过中点之后又完全对称,所以所有 $$r_0 \le r \le n-r_0$$ 都会满足 \(\binom{n}{r} \gt T\)。这个区间中的整数个数是 $$ (n-r_0)-r_0+1 = n+1-2r_0.$$ 这正是实现里在发现第一次越界后加入总答案的数量。

如果扫描一直走到 \(\lfloor n/2 \rfloor\) 仍然没有超过 \(T\),那么这一整行都不贡献任何计数。

具体例子:\(n=23\)

当 \(n=23\) 时,递推依次得到 $$\binom{23}{1}=23,\ \binom{23}{2}=253,\ \binom{23}{3}=1771,\ \binom{23}{4}=8855,\ \binom{23}{5}=33649,$$ $$\binom{23}{6}=100947,\ \binom{23}{7}=245157,\ \binom{23}{8}=490314,\ \binom{23}{9}=817190,\ \binom{23}{10}=1144066.$$ 第一个超过一百万的值出现在 \(r_0=10\)。由对称性可知,与它对应的右半边位置是 \(r=11,12,13\),因此这一行中超过阈值的下标恰好是 $$r=10,11,12,13.$$ 它们的个数为 $$23+1-2\cdot 10=4.$$ 从 1 到 100 的每一行,代码用的都是同样的逻辑。

代码如何工作

参数与校验

C++、Python 和 Java 三个实现都围绕两个数学参数运行:最大行数 \(n_{\max}\) 和阈值 \(T\)。对于题目本身,默认设置是 \(n_{\max}=100\) 与 \(T=10^6\)。另外,三个实现都包含一个很小的校验点:当 \(n_{\max}=10\) 且 \(T=10\) 时,正确答案应为 25。

每一行只维护一个当前系数

固定某个 \(n\) 后,实现先把 \(r=0\) 处的系数设为 \(1\)。然后沿着 $$r=1,2,\dots,\left\lfloor \frac{n}{2}\right\rfloor$$ 向前推进,并用 $$c \leftarrow c\frac{n-r+1}{r}$$ 更新当前值。由于这一步始终得到精确的下一个二项式系数,所以根本不需要浮点数,也不需要额外的二维表。

借助对称性提前结束

一旦当前值首次超过阈值,实现就把 $$n+1-2r$$ 加入总数,并立即结束这一行。这样做是正确的,因为第一次越界已经确定了整段居中的超阈值区间。对于第 53 题,这样还有一个直接好处:中间值始终不大,因为循环在刚超过一百万后就停止了,所以三种语言都可以安全地使用普通整数运算。

复杂度分析

若上界为 \(N\),算法最多进行 $$\sum_{n=1}^{N}\left\lfloor \frac{n}{2}\right\rfloor = O(N^2)$$ 次系数更新。对 \(N=100\) 而言,最坏情况下也只有 2500 次更新,而实际次数还要更少,因为很多行在到达中点之前就已经停止。

空间复杂度是 \(O(1)\)。实现只保存当前系数、累计答案以及少量循环变量和参数。这与实际解法的精简结构完全一致。

脚注与参考资料

  1. 题目页面:Project Euler 53 - Combinatoric selections
  2. 二项式系数:Wikipedia - Binomial coefficient
  3. 帕斯卡三角形:Wikipedia - Pascal's triangle
  4. 组合:Wikipedia - Combination

Краткое содержание задачи

Для каждого целого \(n\) от 1 до 100 и каждого \(r\) с \(0 \le r \le n\) рассматривается биномиальный коэффициент \(\binom{n}{r}\). Нужно подсчитать, сколько пар \((n,r)\) удовлетворяют неравенству \(\binom{n}{r} \gt 10^6\).

Суть решения в том, что не нужно вычислять каждый коэффициент через факториалы. Реализации идут по каждой строке слева направо, останавливаются при первом превышении миллиона и затем по симметрии сразу подсчитывают оставшийся вклад этой строки.

Математический подход

Обозначим порог через \(T=10^6\). Главная структура задачи возникает из связи соседних биномиальных коэффициентов в одной и той же строке.

Как получать строку без факториалов

Определение $$\binom{n}{r}=\frac{n!}{r!(n-r)!}$$ корректно, но для последовательного прохода по строке удобнее сравнить соседние элементы: $$\frac{\binom{n}{r}}{\binom{n}{r-1}}=\frac{n-r+1}{r},$$ откуда $$\binom{n}{r}=\binom{n}{r-1}\frac{n-r+1}{r}.$$ Начиная с \(\binom{n}{0}=1\), мы можем строить строку слева направо по одному значению за шаг.

Именно это и поддерживает код: после очередного обновления текущая переменная равна точному целому значению \(\binom{n}{r}\). Не нужен ни полный треугольник Паскаля, ни массив факториалов.

Почему достаточно левой половины строки

Всю строку определяют два факта: $$\binom{n}{r}=\binom{n}{n-r}$$ и $$\frac{\binom{n}{r}}{\binom{n}{r-1}}=\frac{n-r+1}{r}.$$ Для всех \(1 \le r \le \lfloor n/2 \rfloor\) это отношение больше 1, значит коэффициенты строго возрастают от левого края к середине. После середины те же значения повторяются в обратном порядке из-за симметрии.

Следовательно, как только в левой половине найден первый коэффициент выше порога, положение всех остальных коэффициентов выше порога в этой строке уже известно.

Как первое превышение превращается в число элементов строки

Пусть \(r_0\) есть наименьший индекс, для которого $$\binom{n}{r_0} \gt T.$$ Тогда из возрастания до середины и симметрии после середины следует, что для всех $$r_0 \le r \le n-r_0$$ также верно \(\binom{n}{r} \gt T\). Число таких индексов равно $$ (n-r_0)-r_0+1 = n+1-2r_0.$$ Именно это число реализации добавляют к ответу, как только находят первое превышение.

Если проход дошел до \(\lfloor n/2 \rfloor\), но порог так и не был превышен, то строка дает вклад 0.

Разобранный пример: \(n=23\)

Для \(n=23\) рекуррентное вычисление дает $$\binom{23}{1}=23,\ \binom{23}{2}=253,\ \binom{23}{3}=1771,\ \binom{23}{4}=8855,\ \binom{23}{5}=33649,$$ $$\binom{23}{6}=100947,\ \binom{23}{7}=245157,\ \binom{23}{8}=490314,\ \binom{23}{9}=817190,\ \binom{23}{10}=1144066.$$ Первое значение выше миллиона появляется при \(r_0=10\). По симметрии к нему добавляются отраженные индексы \(r=11,12,13\), поэтому блок превышений равен $$r=10,11,12,13.$$ Его размер равен $$23+1-2\cdot 10=4.$$ Тот же механизм применяется к каждой строке от 1 до 100.

Как работает код

Параметры и контрольная проверка

Реализации на C++, Python и Java используют два математических параметра: верхнюю границу \(n_{\max}\) и порог \(T\). Для задачи Project Euler значения по умолчанию равны \(n_{\max}=100\) и \(T=10^6\). Кроме того, во всех трех версиях есть маленькая проверка: при \(n_{\max}=10\) и \(T=10\) правильный ответ равен 25.

Проход по строке с одним текущим коэффициентом

Для фиксированного \(n\) реализация начинает с коэффициента \(1\) при \(r=0\). Затем она проходит по значениям $$r=1,2,\dots,\left\lfloor \frac{n}{2}\right\rfloor$$ и обновляет текущий коэффициент формулой $$c \leftarrow c\frac{n-r+1}{r}.$$ Поскольку на каждом шаге получается точный следующий биномиальный коэффициент, не требуются ни числа с плавающей точкой, ни заранее построенная таблица.

Досрочное завершение по симметрии

Как только текущий коэффициент превышает порог, реализация прибавляет $$n+1-2r$$ к общему ответу и немедленно заканчивает обработку этой строки. Это корректно, потому что первое превышение уже определяет весь центральный блок значений выше порога. Для задачи 53 такой подход еще и удерживает промежуточные числа маленькими: цикл прекращается сразу после первого значения больше миллиона, поэтому обычной целочисленной арифметики достаточно во всех трех языках.

Анализ сложности

При верхней границе \(N\) алгоритм выполняет не более $$\sum_{n=1}^{N}\left\lfloor \frac{n}{2}\right\rfloor = O(N^2)$$ обновлений коэффициента. Для \(N=100\) это максимум 2500 шагов, а на практике меньше, потому что многие строки завершаются до достижения середины.

Память имеет порядок \(O(1)\): хранятся только текущий коэффициент, накопленный ответ и несколько счетчиков с параметрами. Это полностью соответствует компактной структуре реального решения.

Сноски и ссылки

  1. Условие задачи: Project Euler 53 - Combinatoric selections
  2. Биномиальный коэффициент: Wikipedia - Binomial coefficient
  3. Треугольник Паскаля: Wikipedia - Pascal's triangle
  4. Сочетание: Wikipedia - Combination

ملخص المشكلة

لكل عدد صحيح \(n\) بحيث \(1 \le n \le 100\)، ولكل \(r\) بحيث \(0 \le r \le n\)، ننظر إلى معامل ذات الحدين \(\binom{n}{r}\). المطلوب هو عد جميع الأزواج \((n,r)\) التي تحقق \(\binom{n}{r} \gt 10^6\).

الفكرة الأساسية هي أن الحل لا يحتاج إلى حساب كل قيمة عبر العوامل. التنفيذات تسير عبر كل صف من اليسار إلى اليمين، وتتوقف عند أول قيمة تتجاوز المليون، ثم تستنتج بقية مساهمة الصف مباشرة من خاصية التناظر.

المنهج الرياضي

لنكتب العتبة على صورة \(T=10^6\). البنية المفيدة هنا تأتي من العلاقة بين معاملات ذات الحدين المتتالية داخل الصف نفسه.

توليد الصف من دون استخدام العوامل

التعريف المباشر هو $$\binom{n}{r}=\frac{n!}{r!(n-r)!},$$ لكنه ليس الشكل الأنسب إذا كنا نريد المرور على قيم كثيرة في الصف نفسه. بقسمة حدين متتاليين نحصل على $$\frac{\binom{n}{r}}{\binom{n}{r-1}}=\frac{n-r+1}{r},$$ ومنه $$\binom{n}{r}=\binom{n}{r-1}\frac{n-r+1}{r}.$$ وبالبدء من \(\binom{n}{0}=1\)، يمكننا توليد الصف من اليسار إلى اليمين قيمة بعد أخرى.

هذا هو الثابت الأساسي في الكود: بعد كل تحديث عند قيمة معينة لـ \(r\)، تكون القيمة الجارية مساوية تمامًا لـ \(\binom{n}{r}\). لذلك لا حاجة إلى بناء مثلث باسكال كامل ولا إلى تخزين مصفوفة للعوامل.

لماذا تكفي متابعة النصف الأيسر فقط

يتحكم في الصف كاملًا حقيقتان معروفتان: $$\binom{n}{r}=\binom{n}{n-r}$$ و $$\frac{\binom{n}{r}}{\binom{n}{r-1}}=\frac{n-r+1}{r}.$$ لكل \(1 \le r \le \lfloor n/2 \rfloor\) يكون هذا الكسر أكبر من 1، ولذلك تزداد المعاملات زيادة صارمة كلما انتقلنا من الطرف الأيسر نحو الوسط. وبعد نقطة المنتصف تعيد خاصية التناظر القيم نفسها ولكن بترتيب معكوس.

إذن يكفي العثور على أول قيمة تتجاوز العتبة في النصف الأيسر، لأن ذلك يحدد مباشرة جميع القيم الأخرى في الصف التي تتجاوز العتبة أيضًا.

تحويل أول تجاوز إلى عدد عناصر الصف

ليكن \(r_0\) أصغر فهرس يحقق $$\binom{n}{r_0} \gt T.$$ عندئذ، وبسبب الزيادة حتى الوسط والتناظر بعده، نحصل على أن جميع القيم ذات الفهارس $$r_0 \le r \le n-r_0$$ تحقق كذلك \(\binom{n}{r} \gt T\). وعدد هذه الفهارس يساوي $$ (n-r_0)-r_0+1 = n+1-2r_0.$$ وهذه هي بالضبط الكمية التي تضيفها التنفيذات إلى المجموع عندما تكتشف أول تجاوز.

أما إذا وصل المسح إلى \(\lfloor n/2 \rfloor\) من دون أن تتجاوز أي قيمة العتبة، فمساهمة ذلك الصف تساوي 0.

مثال محلول: \(n=23\)

عندما \(n=23\)، تعطي العلاقة التكرارية القيم $$\binom{23}{1}=23,\ \binom{23}{2}=253,\ \binom{23}{3}=1771,\ \binom{23}{4}=8855,\ \binom{23}{5}=33649,$$ $$\binom{23}{6}=100947,\ \binom{23}{7}=245157,\ \binom{23}{8}=490314,\ \binom{23}{9}=817190,\ \binom{23}{10}=1144066.$$ أول قيمة تتجاوز المليون تظهر عند \(r_0=10\). وبسبب التناظر تظهر القيم المناظرة عند \(r=11,12,13\)، ولذلك تكون كتلة القيم التي تتجاوز العتبة هي $$r=10,11,12,13.$$ وحجم هذه الكتلة يساوي $$23+1-2\cdot 10=4.$$ وهذا هو المنطق نفسه المستخدم في كل صف من 1 إلى 100.

كيف يعمل الكود

المعاملات واختبار التحقق

تنفيذات C++ وPython وJava كلها تعمل باستخدام معاملين رياضيين: الحد الأعلى للصفوف \(n_{\max}\) والعتبة \(T\). في مهمة Project Euler تكون القيم الافتراضية \(n_{\max}=100\) و\(T=10^6\). كما أن كل تنفيذ يحتوي على اختبار تحقق صغير: عندما \(n_{\max}=10\) و\(T=10\)، يجب أن تكون النتيجة الصحيحة هي 25.

المرور على الصف بقيمة جارية واحدة

لكل \(n\) ثابت، يبدأ التنفيذ من المعامل \(1\) عند \(r=0\). ثم يتقدم عبر $$r=1,2,\dots,\left\lfloor \frac{n}{2}\right\rfloor$$ ويحدّث القيمة الجارية وفق العلاقة $$c \leftarrow c\frac{n-r+1}{r}.$$ وبما أن هذه الخطوة تعطي دائمًا معامل ذات الحدين التالي بدقة تامة، فلا حاجة إلى أعداد فاصلة ولا إلى جدول محسوب مسبقًا.

إنهاء مبكر اعتمادًا على التناظر

ما إن تتجاوز القيمة الجارية العتبة حتى يضيف التنفيذ $$n+1-2r$$ إلى المجموع النهائي، ثم يتوقف فورًا عن معالجة ذلك الصف. وهذا صحيح لأن أول تجاوز يحدد كامل الكتلة الوسطى الواقعة فوق العتبة. وفي المسألة 53 يحافظ هذا أيضًا على صغر القيم الوسيطة: فالحل يتوقف مباشرة بعد أول قيمة أكبر من مليون، ولذلك تكفي الحسابات الصحيحة العادية في اللغات الثلاث.

تحليل التعقيد

إذا كان الحد الأعلى هو \(N\)، فإن الخوارزمية تنفذ في أسوأ الأحوال $$\sum_{n=1}^{N}\left\lfloor \frac{n}{2}\right\rfloor = O(N^2)$$ من تحديثات المعاملات. وعند \(N=100\) لا يتجاوز هذا الحد 2500 تحديث، وفي الواقع يكون أقل لأن كثيرًا من الصفوف ينتهي قبل الوصول إلى المنتصف.

استهلاك الذاكرة هو \(O(1)\) فقط، لأن التنفيذ يحتفظ بالمعامل الحالي، والنتيجة التراكمية، وبعض العدادات والمعاملات لا أكثر. وهذا يطابق الطبيعة الخفيفة للحل الفعلي.

حواش ومراجع

  1. صفحة المسألة: Project Euler 53 - Combinatoric selections
  2. معامل ذات الحدين: Wikipedia - Binomial coefficient
  3. مثلث باسكال: Wikipedia - Pascal's triangle
  4. التوافيق: Wikipedia - Combination