Problem Summary

Let \(X_0=1\) and let the sequence decay by independent random factors \(U_1,U_2,\dots\), each uniformly distributed on \((0,1)\), so that

$$X_n=\prod_{k=1}^{n} U_k.$$

The target constant \(c\) is defined by the probability condition

$$\Pr\!\left(X_n\le \frac{1}{c}\right)=0.25\qquad\text{for }n=10^7,$$

and the required output is \(\log_{10} c\). The key observation is that products of independent uniform variables become sums after taking logarithms, which turns the problem into a gamma-distribution quantile computation.

Mathematical Approach

The implementations reduce the random-product statement to a single equation involving the regularized incomplete gamma function. Once that equation is inverted, the answer follows immediately.

Step 1: Convert the product into a sum

Take minus logarithms and define

$$S_n=-\ln X_n=-\sum_{k=1}^{n}\ln U_k=\sum_{k=1}^{n}(-\ln U_k).$$

Therefore the event in the problem becomes

$$X_n\le \frac{1}{c}\iff -\ln X_n\ge \ln c\iff S_n\ge \ln c.$$

So the original probability condition is equivalent to

$$\Pr(S_n\ge \ln c)=0.25.$$

Step 2: Identify the distribution of each logarithmic increment

If \(U\sim \operatorname{Uniform}(0,1)\), then \(Y=-\ln U\) has density

$$f_Y(y)=e^{-y}\qquad (y\ge 0),$$

which is the exponential distribution with rate \(1\). Because the factors are independent, the variables \(Y_1,\dots,Y_n\) are independent as well, and hence

$$S_n=Y_1+\cdots+Y_n\sim \operatorname{Gamma}(n,1),$$

meaning shape \(n\) and scale \(1\).

Step 3: Rewrite the probability with regularized gamma functions

For a gamma variable with shape \(n\) and scale \(1\), the lower and upper regularized incomplete gamma functions are

$$P(n,x)=\frac{\gamma(n,x)}{\Gamma(n)},\qquad Q(n,x)=\frac{\Gamma(n,x)}{\Gamma(n)}=1-P(n,x).$$

They satisfy

$$\Pr(S_n\le x)=P(n,x),\qquad \Pr(S_n\ge x)=Q(n,x).$$

Therefore the defining condition for \(c\) becomes

$$Q(n,\ln c)=0.25,$$

or equivalently

$$P(n,\ln c)=0.75.$$

When \(n\) is a positive integer, the upper tail can also be written as

$$Q(n,x)=e^{-x}\sum_{k=0}^{n-1}\frac{x^k}{k!},$$

but for \(n=10^7\) this identity is conceptually useful rather than computationally attractive.

Step 4: Recognize the answer as a gamma quantile

Set

$$x=\ln c.$$

Then \(x\) is exactly the 75th percentile of the \(\operatorname{Gamma}(n,1)\) distribution:

$$x=P^{-1}(n,0.75).$$

Once \(x\) is known, the requested quantity is not \(c\) itself but its base-10 logarithm, so

$$\log_{10} c=\frac{\ln c}{\ln 10}=\frac{x}{\ln 10}.$$

This is numerically important because \(c\) is astronomically large, while \(\log_{10} c\) is easy to store and print.

Step 5: Large-\(n\) approximation used by two implementations

The C++ implementation computes the gamma quantile directly. The Python and Java implementations instead use the Wilson-Hilferty approximation for a large gamma quantile:

$$x\approx n\left(1-\frac{1}{9n}+\frac{z}{3\sqrt{n}}\right)^3,$$

where

$$z=\Phi^{-1}(0.75)\approx 0.6744897501960817.$$

Because \(n=10^7\) is enormous, this approximation is already very sharp for a result that is ultimately printed to two decimal places.

Worked Example: the checkpoint \(n=100\)

Using the same approximation with \(n=100\), we get

$$x\approx 100\left(1-\frac{1}{900}+\frac{0.6744897502}{30}\right)^3\approx 106.52.$$

Hence

$$\log_{10} c\approx \frac{106.52}{\ln 10}\approx 46.27.$$

This matches the numerical checkpoint used by the exact computation to two decimal places. It also explains why the asymptotic formula is trusted for the much larger target value \(n=10^7\).

How the Code Works

All three implementations first translate the probability question into the gamma-quantile problem \(P(n,x)=0.75\) with \(x=\ln c\), and then return \(x/\ln 10\).

The C++ implementation evaluates that quantile directly with a special-function routine for the inverse lower regularized incomplete gamma function. It also performs two sanity checks: one at \(n=100\), where the expected printed value is \(46.27\) to two decimals, and one at \(n=10^7\), where substituting the computed \(x\) back into the upper regularized gamma function must recover \(0.25\) to high precision.

The Python and Java implementations take the asymptotic route. They hard-code the 75th percentile of the standard normal distribution, plug it into the Wilson-Hilferty cubic approximation for the gamma quantile, and finally divide by \(\ln 10\). That replaces a special-function inversion by a short sequence of elementary floating-point operations.

Complexity Analysis

For the single target input \(n=10^7\), all implementations use \(O(1)\) memory. The Python and Java versions run in \(O(1)\) time with only a handful of arithmetic operations. The C++ version is also \(O(1)\) at the algorithmic level for a fixed \(n\), but its constant factor is larger because it performs a numerical inversion of a special function and then verifies the result with another special-function evaluation.

Footnotes and References

  1. Problem page: https://projecteuler.net/problem=697
  2. Incomplete gamma function: Wikipedia - Incomplete gamma function
  3. Gamma distribution: Wikipedia - Gamma distribution
  4. Exponential distribution: Wikipedia - Exponential distribution
  5. Wilson-Hilferty transformation: Wikipedia - Wilson-Hilferty transformation

Problemzusammenfassung

Sei \(X_0=1\) und die Folge zerfalle durch unabhängige Zufallsfaktoren \(U_1,U_2,\dots\), die jeweils gleichverteilt auf \((0,1)\) sind, also

$$X_n=\prod_{k=1}^{n} U_k.$$

Gesucht ist die Konstante \(c\), die durch

$$\Pr\!\left(X_n\le \frac{1}{c}\right)=0.25,\qquad n=10^7.$$

definiert ist; auszugeben ist \(\log_{10} c\). Der entscheidende Trick besteht darin, das Produkt per Logarithmus in eine Summe umzuwandeln. Danach ist das Problem keine direkte Zufallssimulation mehr, sondern die Inversion eines Gamma-Quantils.

Mathematischer Ansatz

Die Implementierungen führen die Aufgabenstellung auf eine einzige Gleichung mit der regularisierten unvollständigen Gammafunktion zurück. Ist diese Gleichung gelöst, erhält man die gesuchte Zehnerlogarithmus-Ausgabe sofort.

Schritt 1: Das Produkt in eine Summe umformen

Wir betrachten den negativen Logarithmus:

$$S_n=-\ln X_n=-\sum_{k=1}^{n}\ln U_k=\sum_{k=1}^{n}(-\ln U_k).$$

Damit gilt für das Ereignis aus der Aufgabe

$$X_n\le \frac{1}{c}\iff -\ln X_n\ge \ln c\iff S_n\ge \ln c.$$

Die gesuchte Wahrscheinlichkeit lässt sich also schreiben als

$$\Pr(S_n\ge \ln c)=0.25.$$

Schritt 2: Die Verteilung eines einzelnen Summanden bestimmen

Für \(U\sim \operatorname{Uniform}(0,1)\) besitzt \(Y=-\ln U\) die Dichte

$$f_Y(y)=e^{-y}\qquad (y\ge 0),$$

also eine Exponentialverteilung mit Rate \(1\). Wegen der Unabhängigkeit der Faktoren sind auch \(Y_1,\dots,Y_n\) unabhängig, und daher folgt

$$S_n=Y_1+\cdots+Y_n\sim \operatorname{Gamma}(n,1).$$

Die Summe der logarithmischen Zerfallsschritte ist somit gammaverteilt mit Formparameter \(n\) und Skalenparameter \(1\).

Schritt 3: Die Wahrscheinlichkeit mit regularisierten Gammafunktionen ausdrücken

Für eine \(\operatorname{Gamma}(n,1)\)-verteilte Zufallsvariable definieren wir

$$P(n,x)=\frac{\gamma(n,x)}{\Gamma(n)},\qquad Q(n,x)=\frac{\Gamma(n,x)}{\Gamma(n)}=1-P(n,x).$$

Dann gilt

$$\Pr(S_n\le x)=P(n,x),\qquad \Pr(S_n\ge x)=Q(n,x).$$

Die Bedingung der Aufgabe wird also zu

$$Q(n,\ln c)=0.25,$$

beziehungsweise äquivalent

$$P(n,\ln c)=0.75.$$

Für ganzzahliges \(n\) existiert zusätzlich die Darstellung

$$Q(n,x)=e^{-x}\sum_{k=0}^{n-1}\frac{x^k}{k!},$$

die die Verbindung zu einer Poisson-Summe zeigt, aber für \(n=10^7\) nicht die praktischste Rechenform ist.

Schritt 4: Das Gesuchte als Gamma-Quantil erkennen

Setzen wir

$$x=\ln c,$$

dann ist \(x\) genau das 75%-Quantil der Verteilung \(\operatorname{Gamma}(n,1)\):

$$x=P^{-1}(n,0.75).$$

Danach muss nicht \(c\) selbst ausgegeben werden, sondern nur sein Zehnerlogarithmus:

$$\log_{10} c=\frac{\ln c}{\ln 10}=\frac{x}{\ln 10}.$$

Gerade dieser Schritt verhindert den Umgang mit einer gigantischen Zahl \(c\) und reduziert alles auf eine gut skalierte Fließkommazahl.

Schritt 5: Die Groß-\(n\)-Näherung der zwei asymptotischen Implementierungen

Die C++-Implementierung bestimmt das Gamma-Quantil direkt. Die Python- und Java-Implementierungen verwenden stattdessen die Wilson-Hilferty-Näherung

$$x\approx n\left(1-\frac{1}{9n}+\frac{z}{3\sqrt{n}}\right)^3,$$

wobei

$$z=\Phi^{-1}(0.75)\approx 0.6744897501960817.$$

Da \(n=10^7\) extrem groß ist, ist diese Approximation für eine Ausgabe mit zwei Nachkommastellen bereits sehr präzise.

Durchgerechnetes Beispiel: der Kontrollwert \(n=100\)

Mit derselben Formel erhält man für \(n=100\)

$$x\approx 100\left(1-\frac{1}{900}+\frac{0.6744897502}{30}\right)^3\approx 106.52.$$

Daraus folgt

$$\log_{10} c\approx \frac{106.52}{\ln 10}\approx 46.27.$$

Genau dieser Wert wird als Kontrollpunkt verwendet. Das direkte Gamma-Quantil und die asymptotische Formel stimmen hier bereits auf die ausgegebenen zwei Dezimalstellen überein.

Wie der Code funktioniert

Alle drei Implementierungen führen zunächst die Wahrscheinlichkeitsaussage auf das Quantilproblem \(P(n,x)=0.75\) mit \(x=\ln c\) zurück und geben anschließend \(x/\ln 10\) aus.

Die C++-Implementierung verwendet dafür eine Spezialfunktionsroutine, die das inverse untere regularisierte Gamma direkt berechnet. Anschließend wird das Ergebnis zweimal plausibilisiert: einmal für \(n=100\), wo der auszugebende Wert \(46.27\) beträgt, und einmal für \(n=10^7\), wo das zurückeingesetzte \(x\) den oberen Gamma-Anteil \(0.25\) mit hoher Genauigkeit reproduzieren muss.

Die Python- und Java-Implementierungen verzichten auf diese direkte Inversion. Sie setzen stattdessen das 75%-Quantil der Standardnormalverteilung in die Wilson-Hilferty-Formel ein, erhalten so eine Näherung für \(x=\ln c\) und teilen dann durch \(\ln 10\). Rechnerisch bleibt damit nur eine sehr kleine Anzahl elementarer Gleitkommaoperationen.

Komplexitätsanalyse

Für den einzelnen Zielwert \(n=10^7\) benötigen alle Implementierungen \(O(1)\) Speicher. Python und Java laufen in \(O(1)\) Zeit mit nur wenigen arithmetischen Operationen. Auch die C++-Variante ist algorithmisch \(O(1)\) für festes \(n\), hat aber einen größeren konstanten Aufwand, weil ein numerisches Spezialfunktions-Quantil berechnet und anschließend noch überprüft wird.

Fußnoten und Referenzen

  1. Problemseite: https://projecteuler.net/problem=697
  2. Unvollständige Gammafunktion: Wikipedia - Incomplete gamma function
  3. Gamma-Verteilung: Wikipedia - Gamma distribution
  4. Exponentialverteilung: Wikipedia - Exponential distribution
  5. Wilson-Hilferty-Transformation: Wikipedia - Wilson-Hilferty transformation

Problem Özeti

\(X_0=1\) olsun ve dizi, \((0,1)\) aralığında bağımsız ve düzgün dağılan \(U_1,U_2,\dots\) rastgele çarpanlarıyla azalsın:

$$X_n=\prod_{k=1}^{n} U_k.$$

Aranan sabit \(c\),

$$\Pr\!\left(X_n\le \frac{1}{c}\right)=0.25,\qquad n=10^7.$$

koşuluyla tanımlanır; çıktı olarak \(\log_{10} c\) istenir. Temel fikir, devasa bir rastgele çarpımı doğrudan incelemek yerine logaritma alıp problemi gamma dağılımının bir yüzdelik noktasına çevirmektir.

Matematiksel Yaklaşım

Uygulamalar, çarpım formundaki olasılık koşulunu regularized incomplete gamma fonksiyonuyla yazılan tek bir denkleme indirger. O denklem çözüldüğünde cevap doğrudan elde edilir.

Adım 1: Çarpımı toplama dönüştür

Eksi logaritma alalım:

$$S_n=-\ln X_n=-\sum_{k=1}^{n}\ln U_k=\sum_{k=1}^{n}(-\ln U_k).$$

Böylece problemdeki olay

$$X_n\le \frac{1}{c}\iff -\ln X_n\ge \ln c\iff S_n\ge \ln c$$

şeklini alır. Dolayısıyla aranan koşul

$$\Pr(S_n\ge \ln c)=0.25$$

olur.

Adım 2: Her logaritmik artışın dağılımını bul

\(U\sim \operatorname{Uniform}(0,1)\) ise \(Y=-\ln U\) değişkeninin yoğunluğu

$$f_Y(y)=e^{-y}\qquad (y\ge 0)$$

olur; bu da oranı \(1\) olan üstel dağılımdır. Çarpanlar bağımsız olduğundan \(Y_1,\dots,Y_n\) da bağımsızdır. Bu yüzden

$$S_n=Y_1+\cdots+Y_n\sim \operatorname{Gamma}(n,1)$$

elde edilir. Yani toplam, şekil parametresi \(n\), ölçek parametresi \(1\) olan gamma dağılımına uyar.

Adım 3: Olasılığı regularized gamma fonksiyonlarıyla yaz

Alt ve üst regularized incomplete gamma fonksiyonlarını

$$P(n,x)=\frac{\gamma(n,x)}{\Gamma(n)},\qquad Q(n,x)=\frac{\Gamma(n,x)}{\Gamma(n)}=1-P(n,x)$$

diye tanımlayalım. Gamma dağılımı için

$$\Pr(S_n\le x)=P(n,x),\qquad \Pr(S_n\ge x)=Q(n,x)$$

olduğundan problem koşulu

$$Q(n,\ln c)=0.25$$

ve eşdeğer olarak

$$P(n,\ln c)=0.75$$

haline gelir. \(n\) tam sayı olduğunda ayrıca

$$Q(n,x)=e^{-x}\sum_{k=0}^{n-1}\frac{x^k}{k!}$$

yazılabilir; fakat \(n=10^7\) için doğrudan kullanılacak en uygun form bu değildir.

Adım 4: Aranan değerin bir gamma yüzdelik noktası olduğunu gör

$$x=\ln c$$

dersek, \(x\) tam olarak \(\operatorname{Gamma}(n,1)\) dağılımının %75 yüzdelik noktasıdır:

$$x=P^{-1}(n,0.75).$$

Bundan sonra \(c\)'nin kendisini üretmek gerekmiyor; soru zaten \(\log_{10} c\) istediği için

$$\log_{10} c=\frac{\ln c}{\ln 10}=\frac{x}{\ln 10}$$

hesaplanması yeterlidir. Böylece astronomik büyüklükte bir sayı yerine rahatça yazdırılabilen bir logaritma ile çalışırız.

Adım 5: İki uygulamada kullanılan büyük-\(n\) yaklaşımı

C++ uygulaması gamma yüzdelik noktasını doğrudan hesaplar. Python ve Java uygulamaları ise Wilson-Hilferty yaklaşımını kullanır:

$$x\approx n\left(1-\frac{1}{9n}+\frac{z}{3\sqrt{n}}\right)^3,$$

burada

$$z=\Phi^{-1}(0.75)\approx 0.6744897501960817.$$

\(n=10^7\) çok büyük olduğundan bu yaklaşım iki ondalık basamaklık son çıktı için oldukça keskindir.

Çalışılmış Örnek: kontrol noktası \(n=100\)

Aynı yaklaşım \(n=100\) için

$$x\approx 100\left(1-\frac{1}{900}+\frac{0.6744897502}{30}\right)^3\approx 106.52$$

verir. O halde

$$\log_{10} c\approx \frac{106.52}{\ln 10}\approx 46.27.$$

Bu değer, doğrudan gamma terslemesiyle yapılan sayısal kontrolle aynı iki ondalık çıktıyı verir. Bu da çok daha büyük olan \(n=10^7\) için yaklaşımın neden güvenilir olduğunu açıklar.

Kod Nasıl Çalışır

C++, Python ve Java uygulamalarının ortak noktası, önce olasılık sorusunu \(P(n,x)=0.75\) biçimindeki gamma yüzdelik denklemine çevirmeleri ve sonra \(x/\ln 10\) döndürmeleridir.

C++ uygulaması bunu bir özel fonksiyon kütüphanesi aracılığıyla doğrudan çözer. Ardından iki kontrol yapar: \(n=100\) için yazdırılması gereken değerin \(46.27\) olması ve hedef durumda bulunan \(x\) değeri tekrar üst regularized gamma içine yerleştirildiğinde \(0.25\) sonucunun yüksek doğrulukla geri gelmesi.

Python ve Java uygulamaları ise özel fonksiyon terslemesine gitmez. Standart normal dağılımın %75 yüzdelik noktasını sabit olarak alır, bunu Wilson-Hilferty kübik formülüne koyar, yaklaşık \(x=\ln c\) değerini elde eder ve son olarak \(\ln 10\)'a böler. Böylece işlem maliyeti birkaç temel kayan nokta işlemiyle sınırlı kalır.

Karmaşıklık Analizi

Hedef giriş yalnızca tek bir \(n=10^7\) değeri olduğu için tüm uygulamalarda bellek kullanımı \(O(1)\)'dir. Python ve Java sürümleri sabit sayıda aritmetik işlem yaptığı için zaman karmaşıklığı da \(O(1)\)'dir. C++ sürümü de sabit giriş için \(O(1)\) düzeyindedir; ancak gamma yüzdelik terslemesi ve ardından doğrulama hesapları yaptığı için sabit katsayısı daha büyüktür.

Dipnotlar ve Kaynaklar

  1. Problem sayfası: https://projecteuler.net/problem=697
  2. Incomplete gamma function: Wikipedia - Incomplete gamma function
  3. Gamma distribution: Wikipedia - Gamma distribution
  4. Exponential distribution: Wikipedia - Exponential distribution
  5. Wilson-Hilferty transformation: Wikipedia - Wilson-Hilferty transformation

Resumen del Problema

Sea \(X_0=1\) y supongamos que la secuencia decae multiplicando por factores aleatorios independientes \(U_1,U_2,\dots\), cada uno uniforme en \((0,1)\), de modo que

$$X_n=\prod_{k=1}^{n} U_k.$$

La constante buscada \(c\) queda definida por

$$\Pr\!\left(X_n\le \frac{1}{c}\right)=0.25,\qquad n=10^7.$$

y lo que se debe imprimir es \(\log_{10} c\). La idea esencial es transformar el producto aleatorio en una suma mediante logaritmos; así el problema se vuelve un cálculo de cuantiles de una distribución gamma.

Enfoque Matemático

Las implementaciones convierten la condición probabilística original en una sola ecuación con la función gamma incompleta regularizada. Una vez invertida esa ecuación, el resultado pedido sale de forma inmediata.

Paso 1: Convertir el producto en una suma

Tomemos logaritmos con signo negativo:

$$S_n=-\ln X_n=-\sum_{k=1}^{n}\ln U_k=\sum_{k=1}^{n}(-\ln U_k).$$

Entonces el evento de interés se reescribe como

$$X_n\le \frac{1}{c}\iff -\ln X_n\ge \ln c\iff S_n\ge \ln c.$$

Por tanto, la condición del problema equivale a

$$\Pr(S_n\ge \ln c)=0.25.$$

Paso 2: Identificar la distribución de cada incremento logarítmico

Si \(U\sim \operatorname{Uniform}(0,1)\), entonces \(Y=-\ln U\) tiene densidad

$$f_Y(y)=e^{-y}\qquad (y\ge 0),$$

es decir, una distribución exponencial de tasa \(1\). Como los factores son independientes, también lo son \(Y_1,\dots,Y_n\), y en consecuencia

$$S_n=Y_1+\cdots+Y_n\sim \operatorname{Gamma}(n,1).$$

La suma de los pasos de decaimiento en escala logarítmica sigue, por tanto, una gamma con parámetro de forma \(n\) y escala \(1\).

Paso 3: Expresar la probabilidad con funciones gamma regularizadas

Definamos

$$P(n,x)=\frac{\gamma(n,x)}{\Gamma(n)},\qquad Q(n,x)=\frac{\Gamma(n,x)}{\Gamma(n)}=1-P(n,x).$$

Para una variable \(\operatorname{Gamma}(n,1)\), se cumple

$$\Pr(S_n\le x)=P(n,x),\qquad \Pr(S_n\ge x)=Q(n,x).$$

Así, la ecuación que determina \(c\) es

$$Q(n,\ln c)=0.25,$$

o de forma equivalente

$$P(n,\ln c)=0.75.$$

Cuando \(n\) es entero positivo también puede escribirse

$$Q(n,x)=e^{-x}\sum_{k=0}^{n-1}\frac{x^k}{k!},$$

lo cual aclara la estructura algebraica del problema, aunque no es la vía más cómoda para \(n=10^7\).

Paso 4: Reconocer que la incógnita es un cuantil gamma

Si definimos

$$x=\ln c,$$

entonces \(x\) es exactamente el percentil 75 de la distribución \(\operatorname{Gamma}(n,1)\):

$$x=P^{-1}(n,0.75).$$

Una vez hallado \(x\), no hace falta reconstruir \(c\) explícitamente; basta con usar

$$\log_{10} c=\frac{\ln c}{\ln 10}=\frac{x}{\ln 10}.$$

Eso evita trabajar con un número gigantesco y deja todo en una magnitud logarítmica estable.

Paso 5: Aproximación para \(n\) grande usada por dos implementaciones

La implementación en C++ calcula el cuantil gamma de forma directa. Las implementaciones en Python y Java usan la aproximación de Wilson-Hilferty:

$$x\approx n\left(1-\frac{1}{9n}+\frac{z}{3\sqrt{n}}\right)^3,$$

donde

$$z=\Phi^{-1}(0.75)\approx 0.6744897501960817.$$

Como \(n=10^7\) es enorme, esta fórmula ya es suficientemente precisa para un resultado final redondeado a dos decimales.

Ejemplo trabajado: el punto de control \(n=100\)

Aplicando la misma aproximación con \(n=100\), obtenemos

$$x\approx 100\left(1-\frac{1}{900}+\frac{0.6744897502}{30}\right)^3\approx 106.52.$$

Por tanto,

$$\log_{10} c\approx \frac{106.52}{\ln 10}\approx 46.27.$$

Ese valor coincide con el control numérico de la versión exacta a dos decimales, lo que da confianza adicional para el caso objetivo \(n=10^7\).

Cómo Funciona el Código

Las implementaciones en C++, Python y Java comparten el mismo objetivo matemático: resolver \(P(n,x)=0.75\) con \(x=\ln c\) y luego devolver \(x/\ln 10\).

La implementación en C++ usa una rutina de funciones especiales para invertir directamente la gamma regularizada inferior. Después valida el resultado en dos niveles: con \(n=100\), donde la salida esperada es \(46.27\), y con \(n=10^7\), donde al sustituir el \(x\) calculado en la cola superior regularizada debe recuperarse \(0.25\) con alta precisión.

Las implementaciones en Python y Java optan por la aproximación asintótica. Toman el percentil 75 de la normal estándar, lo insertan en la fórmula cúbica de Wilson-Hilferty, obtienen una aproximación para \(x=\ln c\) y finalmente dividen entre \(\ln 10\). El cálculo completo se reduce así a muy pocas operaciones elementales.

Análisis de Complejidad

Para el único valor de entrada \(n=10^7\), todas las implementaciones consumen \(O(1)\) memoria. Las versiones en Python y Java ejecutan \(O(1)\) operaciones aritméticas. La versión en C++ también es \(O(1)\) para un \(n\) fijo, pero con una constante mayor porque realiza una inversión numérica de una función especial y luego una comprobación adicional.

Notas y Referencias

  1. Página del problema: https://projecteuler.net/problem=697
  2. Función gamma incompleta: Wikipedia - Incomplete gamma function
  3. Distribución gamma: Wikipedia - Gamma distribution
  4. Distribución exponencial: Wikipedia - Exponential distribution
  5. Transformación de Wilson-Hilferty: Wikipedia - Wilson-Hilferty transformation

问题概述

设 \(X_0=1\),并且序列每一步都乘上一个独立的随机衰减因子 \(U_1,U_2,\dots\),其中每个 \(U_k\) 都在 \((0,1)\) 上服从均匀分布,于是

$$X_n=\prod_{k=1}^{n} U_k.$$

题目要求的常数 \(c\) 由下式定义:

$$\Pr\!\left(X_n\le \frac{1}{c}\right)=0.25,\qquad n=10^7.$$

最终输出的不是 \(c\) 本身,而是 \(\log_{10} c\)。真正的难点不在于模拟这个随机过程,而在于把随机乘积改写成一个可以稳定求解的分布分位数问题。

数学方法

三份实现都把原始概率条件化成一个关于正则化不完全伽马函数的方程。只要把这个方程反解出来,答案就已经得到。

步骤 1:把随机乘积改写成随机和

对乘积取负对数,定义

$$S_n=-\ln X_n=-\sum_{k=1}^{n}\ln U_k=\sum_{k=1}^{n}(-\ln U_k).$$

于是题目中的事件可以改写为

$$X_n\le \frac{1}{c}\iff -\ln X_n\ge \ln c\iff S_n\ge \ln c.$$

因此原条件完全等价于

$$\Pr(S_n\ge \ln c)=0.25.$$

这一步很关键,因为乘积的分布不直观,而和的分布可以直接识别。

步骤 2:识别每个对数增量的分布

若 \(U\sim \operatorname{Uniform}(0,1)\),令 \(Y=-\ln U\)。容易验证 \(Y\) 的密度为

$$f_Y(y)=e^{-y}\qquad (y\ge 0),$$

也就是参数为 \(1\) 的指数分布。因为各个 \(U_k\) 相互独立,所以对应的 \(Y_1,\dots,Y_n\) 也相互独立,从而

$$S_n=Y_1+\cdots+Y_n\sim \operatorname{Gamma}(n,1).$$

换句话说,衰减过程在对数尺度下变成了 \(n\) 个独立指数变量之和,也就是形状参数为 \(n\)、尺度参数为 \(1\) 的伽马分布。

步骤 3:用正则化不完全伽马函数表达尾概率

对 \(\operatorname{Gamma}(n,1)\) 分布,定义下侧与上侧正则化不完全伽马函数

$$P(n,x)=\frac{\gamma(n,x)}{\Gamma(n)},\qquad Q(n,x)=\frac{\Gamma(n,x)}{\Gamma(n)}=1-P(n,x).$$

它们正好满足

$$\Pr(S_n\le x)=P(n,x),\qquad \Pr(S_n\ge x)=Q(n,x).$$

于是决定 \(c\) 的方程就是

$$Q(n,\ln c)=0.25,$$

等价地写成

$$P(n,\ln c)=0.75.$$

若 \(n\) 是正整数,还可以写出

$$Q(n,x)=e^{-x}\sum_{k=0}^{n-1}\frac{x^k}{k!}.$$

这个公式说明了尾概率与有限级数之间的关系,但当 \(n=10^7\) 时,真正适合计算的方式仍然是直接求分位数,而不是显式展开这一和式。

步骤 4:把未知量看成伽马分布的 75% 分位点

$$x=\ln c,$$

那么 \(x\) 就是 \(\operatorname{Gamma}(n,1)\) 分布的 75% 分位点:

$$x=P^{-1}(n,0.75).$$

一旦求出 \(x\),题目所需的量并不是 \(c\) 本身,而是它的常用对数,因此

$$\log_{10} c=\frac{\ln c}{\ln 10}=\frac{x}{\ln 10}.$$

这一步避免了直接处理一个极其巨大的 \(c\),而只保留数量级信息。

步骤 5:两个近似实现采用的大样本公式

C++ 实现直接求伽马分布分位点;Python 和 Java 实现则利用 Wilson-Hilferty 近似,把大参数伽马分布分位点写成

$$x\approx n\left(1-\frac{1}{9n}+\frac{z}{3\sqrt{n}}\right)^3,$$

其中

$$z=\Phi^{-1}(0.75)\approx 0.6744897501960817.$$

由于目标参数 \(n=10^7\) 极大,这个近似在最终只保留两位小数的场景下已经足够精确。它本质上把伽马分布分位点与标准正态分位点联系起来,从而省掉了专门的特殊函数反演。

算例:检查点 \(n=100\)

把 \(n=100\) 代入同一个近似式,得到

$$x\approx 100\left(1-\frac{1}{900}+\frac{0.6744897502}{30}\right)^3\approx 106.52.$$

于是

$$\log_{10} c\approx \frac{106.52}{\ln 10}\approx 46.27.$$

这个结果与精确分位数计算在两位小数上吻合,因此也解释了为什么在更大的 \(n=10^7\) 下,渐近公式仍然可以作为有效实现方案。

代码如何工作

C++、Python 和 Java 实现虽然数值路线不同,但共同目标完全一致:先求解 \(P(n,x)=0.75\) 中的 \(x=\ln c\),再输出 \(x/\ln 10\)。

C++ 实现走的是精确数值路线。它调用特殊函数库直接反解下侧正则化不完全伽马函数,然后做两类校验:一类是 \(n=100\) 时结果应打印为 \(46.27\);另一类是目标参数 \(n=10^7\) 下,把算出的 \(x\) 代回上侧正则化不完全伽马函数后,必须高精度地返回 \(0.25\)。

Python 和 Java 实现则选择近似路线。它们把标准正态分布的 75% 分位点当作常数,代入 Wilson-Hilferty 三次公式得到 \(x\approx \ln c\),然后除以 \(\ln 10\) 并按两位小数格式输出。这样整套计算只需要很少的浮点运算。

复杂度分析

对于唯一的目标输入 \(n=10^7\),三种实现的空间复杂度都是 \(O(1)\)。Python 和 Java 版本只做常数次基本运算,因此时间复杂度也是 \(O(1)\)。C++ 版本对固定 \(n\) 同样可以视为 \(O(1)\),只是常数项更大,因为它需要执行一次特殊函数的数值反演,并在此后再做一次高精度验证。

脚注与参考资料

  1. 题目页面:https://projecteuler.net/problem=697
  2. 不完全伽马函数:Wikipedia - Incomplete gamma function
  3. 伽马分布:Wikipedia - Gamma distribution
  4. 指数分布:Wikipedia - Exponential distribution
  5. Wilson-Hilferty 变换:Wikipedia - Wilson-Hilferty transformation

Краткое описание

Пусть \(X_0=1\), а затем последовательность убывает за счёт независимых случайных множителей \(U_1,U_2,\dots\), каждый из которых равномерно распределён на \((0,1)\). Тогда

$$X_n=\prod_{k=1}^{n} U_k.$$

Искомая константа \(c\) определяется условием

$$\Pr\!\left(X_n\le \frac{1}{c}\right)=0.25,\qquad n=10^7.$$

а вывести нужно \(\log_{10} c\). Главная идея решения состоит в том, чтобы заменить случайное произведение суммой логарифмов и тем самым свести задачу к вычислению квантили гамма-распределения.

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

Все реализации переводят исходное вероятностное условие в одно уравнение с регуляризованной неполной гамма-функцией. После инверсии этого уравнения ответ получается напрямую.

Шаг 1: Превратить произведение в сумму

Возьмём отрицательный логарифм и введём

$$S_n=-\ln X_n=-\sum_{k=1}^{n}\ln U_k=\sum_{k=1}^{n}(-\ln U_k).$$

Тогда событие из условия переписывается так:

$$X_n\le \frac{1}{c}\iff -\ln X_n\ge \ln c\iff S_n\ge \ln c.$$

Следовательно, исходное требование эквивалентно равенству

$$\Pr(S_n\ge \ln c)=0.25.$$

Шаг 2: Найти распределение одного логарифмического шага

Если \(U\sim \operatorname{Uniform}(0,1)\), то случайная величина \(Y=-\ln U\) имеет плотность

$$f_Y(y)=e^{-y}\qquad (y\ge 0),$$

то есть экспоненциальное распределение с параметром \(1\). Независимость множителей означает независимость \(Y_1,\dots,Y_n\), поэтому

$$S_n=Y_1+\cdots+Y_n\sim \operatorname{Gamma}(n,1).$$

Итак, логарифм случайного затухания оказывается суммой \(n\) независимых экспоненциальных величин.

Шаг 3: Записать вероятность через регуляризованные гамма-функции

Обозначим

$$P(n,x)=\frac{\gamma(n,x)}{\Gamma(n)},\qquad Q(n,x)=\frac{\Gamma(n,x)}{\Gamma(n)}=1-P(n,x).$$

Тогда для распределения \(\operatorname{Gamma}(n,1)\)

$$\Pr(S_n\le x)=P(n,x),\qquad \Pr(S_n\ge x)=Q(n,x).$$

Значит, определяющее условие для \(c\) принимает вид

$$Q(n,\ln c)=0.25,$$

или, что то же самое,

$$P(n,\ln c)=0.75.$$

Для целого \(n\) есть также формула

$$Q(n,x)=e^{-x}\sum_{k=0}^{n-1}\frac{x^k}{k!},$$

но при \(n=10^7\) её удобно рассматривать скорее как теоретическую, чем как вычислительную.

Шаг 4: Увидеть в неизвестном 75%-квантиль гамма-распределения

Положим

$$x=\ln c.$$

Тогда \(x\) есть ровно 75%-квантиль распределения \(\operatorname{Gamma}(n,1)\):

$$x=P^{-1}(n,0.75).$$

После этого остаётся только перейти к десятичному логарифму:

$$\log_{10} c=\frac{\ln c}{\ln 10}=\frac{x}{\ln 10}.$$

Это особенно удобно, потому что само число \(c\) чудовищно велико, а его логарифм остаётся вполне управляемой величиной.

Шаг 5: Приближение для больших \(n\), используемое в двух реализациях

Реализация на C++ вычисляет гамма-квантиль напрямую. Реализации на Python и Java вместо этого используют приближение Уилсона-Хилферти:

$$x\approx n\left(1-\frac{1}{9n}+\frac{z}{3\sqrt{n}}\right)^3,$$

где

$$z=\Phi^{-1}(0.75)\approx 0.6744897501960817.$$

Поскольку здесь \(n=10^7\), эта асимптотическая формула уже даёт очень точный результат для ответа, который печатается лишь с двумя знаками после запятой.

Разобранный пример: контрольная точка \(n=100\)

Подставляя \(n=100\), получаем

$$x\approx 100\left(1-\frac{1}{900}+\frac{0.6744897502}{30}\right)^3\approx 106.52.$$

Следовательно,

$$\log_{10} c\approx \frac{106.52}{\ln 10}\approx 46.27.$$

Именно это значение используется как проверка. Оно совпадает с точным численным вычислением на уровне двух печатаемых десятичных знаков.

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

Реализации на C++, Python и Java опираются на одну и ту же математику: сначала решается уравнение \(P(n,x)=0.75\) для \(x=\ln c\), после чего возвращается \(x/\ln 10\).

Реализация на C++ идёт по точному пути и вызывает специальную численную процедуру для обращения нижней регуляризованной гамма-функции. Затем выполняются две проверки: при \(n=100\) печатаемое значение должно быть \(46.27\), а при \(n=10^7\) подстановка найденного \(x\) в верхнюю регуляризованную гамма-функцию должна вернуть \(0.25\) с высокой точностью.

Реализации на Python и Java используют асимптотику. Они берут 75%-квантиль стандартного нормального распределения, подставляют его в формулу Уилсона-Хилферти, получают приближение для \(x=\ln c\), делят на \(\ln 10\) и форматируют результат до двух десятичных знаков.

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

Для единственного целевого входа \(n=10^7\) все реализации требуют \(O(1)\) памяти. В версиях на Python и Java время работы равно \(O(1)\), поскольку там выполняется лишь константное число элементарных операций. Версия на C++ также является \(O(1)\) для фиксированного \(n\), но её константный множитель больше из-за численного обращения специальной функции и последующей проверки результата.

Сноски и источники

  1. Страница задачи: https://projecteuler.net/problem=697
  2. Неполная гамма-функция: Wikipedia - Incomplete gamma function
  3. Гамма-распределение: Wikipedia - Gamma distribution
  4. Экспоненциальное распределение: Wikipedia - Exponential distribution
  5. Преобразование Уилсона-Хилферти: Wikipedia - Wilson-Hilferty transformation

ملخص المشكلة

لنفرض أن \(X_0=1\)، وأن المتتالية تتناقص في كل خطوة بضربها في عوامل عشوائية مستقلة \(U_1,U_2,\dots\)، حيث كل عامل موزع بانتظام على الفترة \((0,1)\). عندئذ

$$X_n=\prod_{k=1}^{n} U_k.$$

الثابت المطلوب \(c\) يحدده الشرط

$$\Pr\!\left(X_n\le \frac{1}{c}\right)=0.25,\qquad n=10^7.$$

والمطلوب في النهاية هو \(\log_{10} c\) وليس \(c\) نفسه. الفكرة الأساسية هي أن الضرب العشوائي يصبح جمعًا بعد أخذ اللوغاريتم، وبهذا تتحول المسألة إلى مسألة إيجاد رتبة مئوية لتوزيع غاما.

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

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

الخطوة 1: تحويل الجداء إلى مجموع

نأخذ اللوغاريتم السالب ونعرف

$$S_n=-\ln X_n=-\sum_{k=1}^{n}\ln U_k=\sum_{k=1}^{n}(-\ln U_k).$$

وعندها يصبح الحدث في المسألة

$$X_n\le \frac{1}{c}\iff -\ln X_n\ge \ln c\iff S_n\ge \ln c.$$

إذن الشرط المطلوب يكافئ

$$\Pr(S_n\ge \ln c)=0.25.$$

الخطوة 2: تحديد توزيع كل زيادة لوغاريتمية

إذا كان \(U\sim \operatorname{Uniform}(0,1)\)، فإن المتغير \(Y=-\ln U\) يملك الكثافة

$$f_Y(y)=e^{-y}\qquad (y\ge 0),$$

وهذا هو التوزيع الأسي ذو المعدل \(1\). وبما أن العوامل مستقلة، فإن \(Y_1,\dots,Y_n\) مستقلة أيضًا، وبالتالي

$$S_n=Y_1+\cdots+Y_n\sim \operatorname{Gamma}(n,1).$$

أي إن مجموع خطوات التناقص على المقياس اللوغاريتمي يتبع توزيع غاما بمعامل شكل \(n\) ومعامل قياس \(1\).

الخطوة 3: كتابة الاحتمال بدلالة دوال غاما المنتظمة

نعرّف

$$P(n,x)=\frac{\gamma(n,x)}{\Gamma(n)},\qquad Q(n,x)=\frac{\Gamma(n,x)}{\Gamma(n)}=1-P(n,x).$$

وعندئذ لتوزيع \(\operatorname{Gamma}(n,1)\) نحصل على

$$\Pr(S_n\le x)=P(n,x),\qquad \Pr(S_n\ge x)=Q(n,x).$$

ومن ثم يصبح شرط المسألة

$$Q(n,\ln c)=0.25,$$

أو بصورة مكافئة

$$P(n,\ln c)=0.75.$$

وعندما يكون \(n\) عددًا صحيحًا موجبًا يمكن أيضًا كتابة

$$Q(n,x)=e^{-x}\sum_{k=0}^{n-1}\frac{x^k}{k!},$$

لكن هذه الصيغة مفيدة للفهم أكثر من كونها الصيغة الأنسب للحساب عندما يكون \(n=10^7\).

الخطوة 4: ملاحظة أن المجهول هو رتبة مئوية لتوزيع غاما

لنكتب

$$x=\ln c.$$

عندها يكون \(x\) هو تمامًا الرتبة المئوية \(75\%\) لتوزيع \(\operatorname{Gamma}(n,1)\):

$$x=P^{-1}(n,0.75).$$

وبعد إيجاد \(x\)، لا نحتاج إلى تكوين \(c\) نفسه، لأن المطلوب أصلًا هو

$$\log_{10} c=\frac{\ln c}{\ln 10}=\frac{x}{\ln 10}.$$

وهذا يوفر التعامل مع عدد هائل جدًا ويُبقي الحساب في صورة لوغاريتمية مستقرة.

الخطوة 5: التقريب المستخدم في تطبيقين عند \(n\) الكبير

تطبيق C++ يحسب رتبة غاما المئوية مباشرة. أما تطبيقا Python وJava فيستخدمان تقريب ويلسون-هيلفرتي:

$$x\approx n\left(1-\frac{1}{9n}+\frac{z}{3\sqrt{n}}\right)^3,$$

حيث

$$z=\Phi^{-1}(0.75)\approx 0.6744897501960817.$$

وبما أن \(n=10^7\) كبير جدًا، فإن هذا التقريب يكون دقيقًا بما يكفي لنتيجة نهائية ستُطبع مع منزلتين عشريتين فقط.

مثال محلول: نقطة التحقق \(n=100\)

عند التعويض بـ \(n=100\) في الصيغة نفسها نحصل على

$$x\approx 100\left(1-\frac{1}{900}+\frac{0.6744897502}{30}\right)^3\approx 106.52.$$

ومن ثم

$$\log_{10} c\approx \frac{106.52}{\ln 10}\approx 46.27.$$

وهذه هي قيمة التحقق المعتمدة في الحساب الدقيق أيضًا، ولذلك فهي تعطي ثقة إضافية في استخدام الصيغة التقريبية عندما يكون \(n=10^7\).

كيف تعمل الشيفرة

تتفق تطبيقات C++ وPython وJava في الجوهر الرياضي: أولًا يُحل الشرط \(P(n,x)=0.75\) من أجل \(x=\ln c\)، ثم يُعاد الناتج على صورة \(x/\ln 10\).

تطبيق C++ يسلك الطريق العددي الدقيق، فيستخدم أداة دوال خاصة لعكس دالة غاما المنتظمة السفلية مباشرة. بعد ذلك يجري تحققين: تحققًا عند \(n=100\) حيث يجب أن تكون القيمة المطبوعة \(46.27\)، وتحققًا عند \(n=10^7\) بحيث تؤدي إعادة إدخال \(x\) في ذيل غاما العلوي المنتظم إلى استرجاع \(0.25\) بدقة عالية.

أما تطبيقا Python وJava فيسلكان طريق التقريب. فهما يأخذان الرتبة المئوية \(75\%\) للتوزيع الطبيعي المعياري، ويضعانها في صيغة ويلسون-هيلفرتي التكعيبية للحصول على تقريب لـ \(x=\ln c\)، ثم يقسمان على \(\ln 10\) ويطبعان الناتج حتى منزلتين عشريتين.

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

للقيمة المستهدفة الوحيدة \(n=10^7\)، تحتاج جميع التطبيقات إلى ذاكرة \(O(1)\). تطبيقا Python وJava ينفذان عددًا ثابتًا من العمليات الحسابية، لذا زمنهما \(O(1)\). أما تطبيق C++ فهو أيضًا \(O(1)\) عند اعتبار \(n\) ثابتًا، لكن معامل الزمن الثابت فيه أكبر لأنه يتضمن عكسًا عدديًا لدالة خاصة ثم خطوة تحقق إضافية.

هوامش ومراجع

  1. صفحة المسألة: https://projecteuler.net/problem=697
  2. الدالة غاما غير التامة: Wikipedia - Incomplete gamma function
  3. توزيع غاما: Wikipedia - Gamma distribution
  4. التوزيع الأسي: Wikipedia - Exponential distribution
  5. تحويل ويلسون-هيلفرتي: Wikipedia - Wilson-Hilferty transformation