Problem Summary

For a decimal prefix \(L\), let \(p(L,n)\) denote the exponent \(j\) of the \(n\)-th power of two whose leading decimal digits are exactly \(L\). Problem 686 asks for \(p(123,678910)\).

A direct search by forming huge powers of two is unnecessary. The decisive observation is that leading digits depend only on the fractional part of a base-10 logarithm, so the whole task becomes an interval-hitting problem on \([0,1)\).

Mathematical Approach

Write

$$\theta=\log_{10}2.$$

For each exponent \(j\), the decimal size of \(2^j\) is governed by \(j\theta\).

Step 1: Turn the Prefix Condition into a Logarithmic Window

Assume \(L\) has \(d\) decimal digits. Saying that \(2^j\) begins with the digits \(L\) means that for some integer \(m\),

$$L\cdot 10^m \le 2^j \lt (L+1)\cdot 10^m.$$

Taking \(\log_{10}\) gives

$$\log_{10}L + m \le j\theta \lt \log_{10}(L+1) + m.$$

Because \(L\) has \(d\) digits, the relevant normalization is obtained by subtracting \(d-1\). Therefore the condition is equivalent to

$$a \le \{j\theta\} \lt b,$$

where

$$a=\log_{10}L-(d-1), \qquad b=\log_{10}(L+1)-(d-1),$$

and \(\{x\}\) denotes the fractional part of \(x\). So the leading-digit question has been reduced to testing whether \(\{j\theta\}\) lands inside one fixed half-open interval.

Step 2: View the Exponents as a Rotation Modulo 1

Define

$$r_j=\{j\theta\}.$$

Then

$$r_{j+1}=\{r_j+\theta\}, \qquad r_0=0.$$

This is just repeated rotation by the constant angle \(\theta\) on the unit interval. Each time \(r_j\) falls in \([a,b)\), the power \(2^j\) starts with \(L\). Hence \(p(L,n)\) is exactly the index of the \(n\)-th visit of this orbit to that interval.

Step 3: Why Fractional Parts Are Enough

The integer part of \(j\theta\) only tells us how many digits \(2^j\) has. The first few digits come from the mantissa \(10^{\{j\theta\}}\). In fact,

$$2^j = 10^{\lfloor j\theta \rfloor + \{j\theta\}} = 10^{\lfloor j\theta \rfloor}\cdot 10^{\{j\theta\}}.$$

Therefore the prefix is determined entirely by the number \(10^{\{j\theta\}}\in[1,10)\). For the target prefix \(123\), the interval used by the implementations is

$$a=\log_{10}(123)-2 \approx 0.08990511143939792,$$

$$b=\log_{10}(124)-2 \approx 0.09342168516225026.$$

Whenever \(\{j\theta\}\) falls inside that window, the mantissa lies in \([1.23,1.24)\), which means the decimal expansion of \(2^j\) begins with \(123\).

Step 4: Worked Example with \(L=12\)

For \(L=12\), we have \(d=2\), so

$$a=\log_{10}(12)-1 \approx 0.07918124604762482,$$

$$b=\log_{10}(13)-1 \approx 0.11394335230683678.$$

Now check a few exponents. For \(j=7\),

$$7\theta \approx 2.1072099696478683,$$

so

$$\{7\theta\}\approx 0.1072099696478683 \in [a,b).$$

That predicts that \(2^7\) begins with \(12\), and indeed \(2^7=128\).

For \(j=80\),

$$80\theta \approx 24.082399653118495,$$

hence

$$\{80\theta\}\approx 0.082399653118495 \in [a,b).$$

So the second hit occurs at \(j=80\), matching the small checkpoints used by the implementation.

Step 5: Apply the Same Mechanism to the Target Query

Nothing changes for \(L=123\) except the interval endpoints. We keep iterating the orbit \(r_{j+1}=\{r_j+\theta\}\), count how many times it enters the window for prefix \(123\), and stop at the \(678910\)-th hit. No large integer powers need to be constructed, because the logarithmic window already captures the leading digits exactly.

How the Code Works

The C++, Python, and Java implementations all follow the same mathematical plan: convert the prefix condition into a logarithmic interval, iterate the fractional parts of successive multiples of \(\log_{10}2\), and count interval hits until the requested occurrence is reached.

The C++ implementation computes the number of digits of the prefix and the two interval endpoints at runtime, then advances one exponent at a time using extended floating-point precision. It also checks small benchmark cases such as \(p(12,1)=7\), \(p(12,2)=80\), and \(p(123,45)=12710\) before producing the final answer.

The Python implementation uses the same floating-point recurrence but is specialized directly to the target query \(L=123\), \(n=678910\). The Java implementation is more specialized still: it stores scaled integer approximations of \(\log_{10}2\) and of the two interval endpoints for prefix \(123\). Since the upper endpoint for \(123\) is smaller than \(\log_{10}2\), every successful hit must occur immediately after a wraparound, so the integer-based version only needs to test wrapped updates.

Complexity Analysis

Each iteration performs a constant number of additions, comparisons, and at most one wraparound reduction. Therefore the running time is \(O(p(L,n))\), because we advance through exponents until the desired hit is found. The memory usage is \(O(1)\), since only a few scalar values are maintained throughout the search.

Footnotes and References

  1. Project Euler problem page: https://projecteuler.net/problem=686
  2. Common logarithm: Wikipedia - Common logarithm
  3. Fractional part: Wikipedia - Fractional part
  4. Equidistribution theorem: Wikipedia - Equidistribution theorem
  5. Benford's law: Wikipedia - Benford's law

Problemzusammenfassung

Für ein Dezimalpräfix \(L\) bezeichnet \(p(L,n)\) den Exponenten \(j\), bei dem zum \(n\)-ten Mal eine Zweierpotenz mit genau diesen Anfangsziffern beginnt. In Problem 686 ist also \(p(123,678910)\) gesucht.

Man muss dafür keine riesigen Zahlen \(2^j\) direkt ausrechnen. Entscheidend ist, dass die führenden Ziffern nur von der Nachkommastelle eines Zehnerlogarithmus abhängen. Damit wird die Aufgabe zu einem Intervalltrefferproblem auf \([0,1)\).

Mathematischer Ansatz

Setze

$$\theta=\log_{10}2.$$

Dann wird die Dezimalstruktur von \(2^j\) vollständig durch \(j\theta\) beschrieben.

Step 1: Präfixbedingung als logarithmisches Fenster

Hat \(L\) genau \(d\) Stellen, dann bedeutet „\(2^j\) beginnt mit \(L\)“, dass es ein ganzzahliges \(m\) gibt mit

$$L\cdot 10^m \le 2^j \lt (L+1)\cdot 10^m.$$

Nach Anwendung von \(\log_{10}\) erhält man

$$\log_{10}L + m \le j\theta \lt \log_{10}(L+1) + m.$$

Da \(L\) \(d\) Stellen besitzt, normiert man durch \(d-1\). Genau dann gilt

$$a \le \{j\theta\} \lt b,$$

wobei

$$a=\log_{10}L-(d-1), \qquad b=\log_{10}(L+1)-(d-1)$$

und \(\{x\}\) den Nachkommateil bezeichnet. Aus einer Ziffernfrage wird also ein Test, ob die gebrochene Komponente \(\{j\theta\}\) in einem festen halboffenen Intervall liegt.

Step 2: Rotation modulo 1

Definiere

$$r_j=\{j\theta\}.$$

Dann folgt unmittelbar die Rekursion

$$r_{j+1}=\{r_j+\theta\}, \qquad r_0=0.$$

Wir drehen also bei jedem Schritt um denselben Betrag \(\theta\) auf dem Einheitsintervall. Immer wenn \(r_j\) in \([a,b)\) landet, beginnt \(2^j\) mit \(L\). Damit ist \(p(L,n)\) genau der Index des \(n\)-ten Treffers dieses Orbits.

Step 3: Warum nur der Nachkommateil zählt

Der ganzzahlige Teil von \(j\theta\) sagt nur, wie viele Stellen \(2^j\) hat. Die ersten Ziffern stammen aus der Mantisse \(10^{\{j\theta\}}\). Es gilt nämlich

$$2^j = 10^{\lfloor j\theta \rfloor + \{j\theta\}} = 10^{\lfloor j\theta \rfloor}\cdot 10^{\{j\theta\}}.$$

Somit bestimmt allein \(10^{\{j\theta\}}\in[1,10)\) das Präfix. Für das Zielpräfix \(123\) verwenden die Implementierungen das Intervall

$$a=\log_{10}(123)-2 \approx 0.08990511143939792,$$

$$b=\log_{10}(124)-2 \approx 0.09342168516225026.$$

Liegt \(\{j\theta\}\) in diesem Bereich, dann liegt die Mantisse in \([1.23,1.24)\), also beginnt \(2^j\) tatsächlich mit \(123\).

Step 4: Durchgerechnetes Beispiel mit \(L=12\)

Für \(L=12\) ist \(d=2\), also

$$a=\log_{10}(12)-1 \approx 0.07918124604762482,$$

$$b=\log_{10}(13)-1 \approx 0.11394335230683678.$$

Bei \(j=7\) erhält man

$$7\theta \approx 2.1072099696478683,$$

also

$$\{7\theta\}\approx 0.1072099696478683 \in [a,b).$$

Damit beginnt \(2^7\) mit \(12\), und tatsächlich ist \(2^7=128\).

Für \(j=80\) gilt

$$80\theta \approx 24.082399653118495,$$

sodass

$$\{80\theta\}\approx 0.082399653118495 \in [a,b).$$

Der zweite Treffer liegt also bei \(j=80\). Genau diese kleinen Kontrollwerte werden in der Implementierung verwendet.

Step 5: Übergang zur eigentlichen Zielanfrage

Für \(L=123\) ändert sich nur das Intervall. Man iteriert weiterhin \(r_{j+1}=\{r_j+\theta\}\), zählt die Treffer im Fenster für \(123\) und stoppt beim \(678910\)-ten Treffer. Große Ganzzahlen werden dabei nie benötigt, weil die Logarithmen die Anfangsziffern bereits vollständig kodieren.

Wie der Code funktioniert

Die C++-, Python- und Java-Implementierungen folgen alle demselben Plan: Zuerst werden aus dem Präfix zwei logarithmische Intervallgrenzen gebildet, danach werden die Nachkommateile sukzessiver Vielfacher von \(\log_{10}2\) iteriert und die Treffer gezählt.

Die C++-Implementierung berechnet Stellenzahl und Intervallgrenzen zur Laufzeit und arbeitet dann Exponent für Exponent mit erhöhter Gleitkommapräzision ab. Außerdem prüft sie vor der Endausgabe kleine Referenzwerte wie \(p(12,1)=7\), \(p(12,2)=80\) und \(p(123,45)=12710\).

Die Python-Implementierung verwendet denselben Gleitkomma-Ansatz, ist aber direkt auf die Zielanfrage \(L=123\), \(n=678910\) spezialisiert. Die Java-Implementierung geht noch einen Schritt weiter und speichert skalierte Ganzzahlapproximationen von \(\log_{10}2\) sowie der beiden Intervallgrenzen für das Präfix \(123\). Weil die obere Grenze für \(123\) kleiner als \(\log_{10}2\) ist, kann ein Treffer nur unmittelbar nach einem Überlauf auftreten; deshalb prüft die ganzzahlige Variante nur diese gewrappten Schritte.

Komplexitätsanalyse

Jede Iteration besteht nur aus konstant vielen Additionen, Vergleichen und höchstens einer Reduktion modulo \(1\). Daher beträgt die Laufzeit \(O(p(L,n))\), denn es werden alle Exponenten bis zum gesuchten Treffer durchlaufen. Der Speicherbedarf ist \(O(1)\), weil nur wenige skalare Zustandsgrößen gehalten werden.

Fußnoten und Quellen

  1. Projekt-Euler-Aufgabe: https://projecteuler.net/problem=686
  2. Zehnerlogarithmus: Wikipedia - Common logarithm
  3. Nachkommateil: Wikipedia - Fractional part
  4. Gleichverteilungssatz: Wikipedia - Equidistribution theorem
  5. Benfordsches Gesetz: Wikipedia - Benford's law

Problem Özeti

Bir ondalık önek \(L\) için \(p(L,n)\), soldan ilk basamakları tam olarak \(L\) olan \(n\). iki kuvvetinin üssü \(j\) anlamına gelir. Problem 686, \(p(123,678910)\) değerini sorar.

Bunu çözmek için devasa büyüklükte \(2^j\) sayıları üretmek gerekmez. Belirleyici nokta şudur: ilk basamaklar yalnızca taban-10 logaritmanın kesir kısmına bağlıdır. Böylece problem \([0,1)\) aralığında bir pencereye düşme sayımı haline gelir.

Matematiksel Yaklaşım

Şunu tanımlayalım:

$$\theta=\log_{10}2.$$

Her \(j\) için \(2^j\) sayısının ondalık yapısı \(j\theta\) üzerinden okunur.

Step 1: Önek Koşulunu Logaritmik Bir Pencereye Çevirme

\(L\) sayısı \(d\) basamaklı olsun. “\(2^j\) sayısı \(L\) ile başlar” demek, bir tamsayı \(m\) için

$$L\cdot 10^m \le 2^j \lt (L+1)\cdot 10^m$$

olması demektir. Her iki tarafa \(\log_{10}\) uygulanınca

$$\log_{10}L + m \le j\theta \lt \log_{10}(L+1) + m$$

elde edilir. \(L\) \(d\) basamaklı olduğundan, uygun normalizasyon \(d-1\) çıkarılarak yapılır. Böylece koşul

$$a \le \{j\theta\} \lt b$$

eşdeğerliğine iner; burada

$$a=\log_{10}L-(d-1), \qquad b=\log_{10}(L+1)-(d-1)$$

ve \(\{x\}\), \(x\)'in kesir kısmıdır. Yani ilk basamakları test etmek yerine sabit bir yarı açık aralığa düşmeyi test ederiz.

Step 2: Mod 1 Dönüşü Olarak Görmek

Şimdi

$$r_j=\{j\theta\}$$

tanımını yapalım. O zaman

$$r_{j+1}=\{r_j+\theta\}, \qquad r_0=0$$

olur. Bu, birim aralık üzerinde her adımda aynı miktar kadar dönmek demektir. \(r_j\) değeri \([a,b)\) penceresine her girdiğinde, \(2^j\) sayısı \(L\) ile başlar. Dolayısıyla \(p(L,n)\), bu pencereye yapılan \(n\). ziyaretin indisidir.

Step 3: Neden Sadece Kesir Kısmı Yeterlidir

\(j\theta\)'nın tam kısmı yalnızca \(2^j\)'nin kaç basamaklı olduğunu söyler. İlk basamakları belirleyen kısım, mantissa olan \(10^{\{j\theta\}}\) ifadesidir. Gerçekten de

$$2^j = 10^{\lfloor j\theta \rfloor + \{j\theta\}} = 10^{\lfloor j\theta \rfloor}\cdot 10^{\{j\theta\}}.$$

Bu yüzden önek tamamen \(10^{\{j\theta\}}\in[1,10)\) değeriyle belirlenir. Hedef önek \(123\) için uygulamaların kullandığı pencere

$$a=\log_{10}(123)-2 \approx 0.08990511143939792,$$

$$b=\log_{10}(124)-2 \approx 0.09342168516225026$$

şeklindedir. \(\{j\theta\}\) bu aralığa düştüğünde mantissa \([1.23,1.24)\) içinde olur; yani ondalık gösterim kesin olarak \(123\) ile başlar.

Step 4: \(L=12\) için Çözümlü Örnek

\(L=12\) olduğunda \(d=2\) ve

$$a=\log_{10}(12)-1 \approx 0.07918124604762482,$$

$$b=\log_{10}(13)-1 \approx 0.11394335230683678$$

olur. \(j=7\) için

$$7\theta \approx 2.1072099696478683$$

ve dolayısıyla

$$\{7\theta\}\approx 0.1072099696478683 \in [a,b).$$

Bu, \(2^7\) sayısının \(12\) ile başlaması gerektiğini söyler; gerçekten \(2^7=128\).

\(j=80\) için ise

$$80\theta \approx 24.082399653118495,$$

yani

$$\{80\theta\}\approx 0.082399653118495 \in [a,b).$$

Böylece ikinci eşleşme \(j=80\)'de gelir. Uygulamadaki küçük kontrol değerleri tam olarak budur.

Step 5: Aynı Mekanizmayı Hedef Sorguya Uygulama

\(L=123\) için değişen tek şey pencerenin uçlarıdır. Yine \(r_{j+1}=\{r_j+\theta\}\) yinelemesini sürdürür, \(123\) penceresine düşüşleri sayar ve \(678910\). düşüşte dururuz. Büyük tam sayılar üretmeye ihtiyaç yoktur; logaritmik pencere ilk basamak bilgisini zaten tam olarak taşır.

Kod Nasıl Çalışır

C++, Python ve Java implementasyonlarının hepsi aynı matematiksel planı izler: önek koşulu iki logaritmik sınırla ifade edilir, sonra \(\log_{10}2\)'nin ardışık katlarının kesir kısımları üretilir ve pencereye girenler sayılır.

C++ implementasyonu önekin basamak sayısını ve iki sınırı çalışma anında hesaplar, ardından üsleri tek tek ilerleterek daha yüksek duyarlıklı kayan nokta aritmetiğiyle tarar. Sonuca geçmeden önce \(p(12,1)=7\), \(p(12,2)=80\) ve \(p(123,45)=12710\) gibi küçük doğrulama değerlerini de kontrol eder.

Python implementasyonu aynı kayan nokta yaklaşımını doğrudan hedef sorgu \(L=123\), \(n=678910\) için uygular. Java implementasyonu daha da özelleşmiştir: \(\log_{10}2\) ile \(123\) için gereken iki sınırı \(10^{18}\) ölçeğinde tam sayılar olarak saklar. \(123\) için üst sınır \(\log_{10}2\)'den küçük olduğu için her isabet yalnızca bir sarmalama adımından hemen sonra oluşabilir; bu yüzden tam sayı tabanlı sürüm sadece bu sarılan adımları test eder.

Karmaşıklık Analizi

Her iterasyonda sabit sayıda toplama, karşılaştırma ve en fazla bir kez sarmalama düzeltmesi yapılır. Bu nedenle çalışma süresi \(O(p(L,n))\) olur; çünkü aranan isabete ulaşana kadar üsleri sırayla gezeriz. Bellek kullanımı \(O(1)\)'dir; süreç boyunca yalnızca birkaç skaler değer tutulur.

Dipnotlar ve Kaynaklar

  1. Project Euler problem sayfası: https://projecteuler.net/problem=686
  2. Ondalık logaritma: Wikipedia - Common logarithm
  3. Kesir kısmı: Wikipedia - Fractional part
  4. Eşdağılım teoremi: Wikipedia - Equidistribution theorem
  5. Benford yasası: Wikipedia - Benford's law

Resumen del Problema

Para un prefijo decimal \(L\), \(p(L,n)\) es el exponente \(j\) de la \(n\)-ésima potencia de dos cuya expansión decimal empieza exactamente por \(L\). En el problema 686 se pide \(p(123,678910)\).

No hace falta construir potencias gigantes de dos. La clave es que los primeros dígitos dependen solo de la parte fraccionaria de un logaritmo en base 10, de modo que todo se reduce a contar visitas a un intervalo fijo de \([0,1)\).

Enfoque Matemático

Escribimos

$$\theta=\log_{10}2.$$

Entonces la forma decimal de \(2^j\) queda codificada por \(j\theta\).

Step 1: Convertir el Prefijo en una Ventana Logarítmica

Supongamos que \(L\) tiene \(d\) cifras. Decir que \(2^j\) empieza por \(L\) significa que existe un entero \(m\) tal que

$$L\cdot 10^m \le 2^j \lt (L+1)\cdot 10^m.$$

Al tomar \(\log_{10}\), obtenemos

$$\log_{10}L + m \le j\theta \lt \log_{10}(L+1) + m.$$

Como \(L\) tiene \(d\) cifras, la normalización adecuada consiste en restar \(d-1\). Así, la condición es equivalente a

$$a \le \{j\theta\} \lt b,$$

donde

$$a=\log_{10}L-(d-1), \qquad b=\log_{10}(L+1)-(d-1),$$

y \(\{x\}\) denota la parte fraccionaria de \(x\). La pregunta sobre los primeros dígitos se convierte entonces en un simple test de pertenencia a un intervalo semiabierto fijo.

Step 2: Interpretarlo como una Rotación Módulo 1

Definimos

$$r_j=\{j\theta\}.$$

Entonces

$$r_{j+1}=\{r_j+\theta\}, \qquad r_0=0.$$

Eso es una rotación repetida por el mismo desplazamiento \(\theta\) en el intervalo unidad. Cada vez que \(r_j\) entra en \([a,b)\), la potencia \(2^j\) tiene prefijo \(L\). Por tanto, \(p(L,n)\) es exactamente el índice de la \(n\)-ésima visita de esta órbita a esa ventana.

Step 3: Por Qué Basta la Parte Fraccionaria

La parte entera de \(j\theta\) solo indica cuántas cifras tiene \(2^j\). Los primeros dígitos vienen dados por la mantisa \(10^{\{j\theta\}}\). De hecho,

$$2^j = 10^{\lfloor j\theta \rfloor + \{j\theta\}} = 10^{\lfloor j\theta \rfloor}\cdot 10^{\{j\theta\}}.$$

Así que el prefijo depende únicamente del valor \(10^{\{j\theta\}}\in[1,10)\). Para el prefijo objetivo \(123\), la ventana usada por las implementaciones es

$$a=\log_{10}(123)-2 \approx 0.08990511143939792,$$

$$b=\log_{10}(124)-2 \approx 0.09342168516225026.$$

Cuando \(\{j\theta\}\) cae en ese intervalo, la mantisa pertenece a \([1.23,1.24)\), lo que obliga a que la expansión decimal de \(2^j\) empiece por \(123\).

Step 4: Ejemplo Trabajado con \(L=12\)

Si \(L=12\), entonces \(d=2\) y

$$a=\log_{10}(12)-1 \approx 0.07918124604762482,$$

$$b=\log_{10}(13)-1 \approx 0.11394335230683678.$$

Para \(j=7\),

$$7\theta \approx 2.1072099696478683,$$

así que

$$\{7\theta\}\approx 0.1072099696478683 \in [a,b).$$

Eso predice que \(2^7\) empieza por \(12\), y en efecto \(2^7=128\).

Para \(j=80\),

$$80\theta \approx 24.082399653118495,$$

por lo que

$$\{80\theta\}\approx 0.082399653118495 \in [a,b).$$

Así, el segundo acierto ocurre en \(j=80\), exactamente como indican las comprobaciones pequeñas de la implementación.

Step 5: Aplicación Directa al Caso \(L=123\)

Para \(L=123\) el mecanismo no cambia; solo cambian los extremos de la ventana. Se itera \(r_{j+1}=\{r_j+\theta\}\), se cuentan las visitas al intervalo correspondiente a \(123\) y se detiene el proceso en la visita número \(678910\). Nunca hace falta calcular el entero gigantesco \(2^j\), porque la ventana logarítmica ya contiene toda la información sobre los primeros dígitos.

Cómo Funciona el Código

Las implementaciones en C++, Python y Java siguen exactamente este esquema: convierten la condición de prefijo en dos límites logarítmicos, generan las partes fraccionarias de múltiplos sucesivos de \(\log_{10}2\) y cuentan cuántas veces caen dentro del intervalo objetivo.

La implementación en C++ calcula en tiempo de ejecución el número de cifras del prefijo y los dos extremos del intervalo, y luego avanza exponente por exponente usando aritmética de coma flotante de mayor precisión. Antes de imprimir la respuesta final también verifica casos pequeños como \(p(12,1)=7\), \(p(12,2)=80\) y \(p(123,45)=12710\).

La implementación en Python usa la misma recurrencia en coma flotante, pero ya especializada al caso final \(L=123\), \(n=678910\). La implementación en Java está aún más especializada: guarda aproximaciones enteras escaladas de \(\log_{10}2\) y de los dos extremos del intervalo para \(123\). Como el extremo superior para \(123\) es menor que \(\log_{10}2\), todo acierto debe aparecer justo después de una vuelta completa, así que la versión entera solo comprueba los pasos que hacen wraparound.

Análisis de Complejidad

Cada iteración realiza un número constante de sumas, comparaciones y, como mucho, una reducción por wraparound. Por ello, el tiempo total es \(O(p(L,n))\), ya que se recorren exponentes hasta encontrar el acierto deseado. La memoria es \(O(1)\), porque durante todo el proceso solo se mantienen unas pocas cantidades escalares.

Notas y Referencias

  1. Página del problema: https://projecteuler.net/problem=686
  2. Logaritmo decimal: Wikipedia - Common logarithm
  3. Parte fraccionaria: Wikipedia - Fractional part
  4. Teorema de equidistribución: Wikipedia - Equidistribution theorem
  5. Ley de Benford: Wikipedia - Benford's law

问题概述

对一个十进制前缀 \(L\),记 \(p(L,n)\) 为第 \(n\) 个满足“\(2^j\) 的十进制表示恰好以 \(L\) 开头”的指数 \(j\)。第 686 题要求的就是 \(p(123,678910)\)。

这道题表面上像是在寻找非常巨大的二次幂,但真正决定首位数字的并不是 \(2^j\) 的完整值,而是它的十进制对数的小数部分。因此问题可以改写成:在单位区间上不断做一次固定旋转,统计轨道有多少次落入某个小窗口。

数学方法

$$\theta=\log_{10}2.$$

那么每个指数 \(j\) 对应的十进制结构都由 \(j\theta\) 决定。

Step 1: 把前缀条件改写成对数区间

设前缀 \(L\) 有 \(d\) 位。说“\(2^j\) 以 \(L\) 开头”,等价于存在某个整数 \(m\),使得

$$L\cdot 10^m \le 2^j \lt (L+1)\cdot 10^m.$$

对不等式取 \(\log_{10}\),得到

$$\log_{10}L + m \le j\theta \lt \log_{10}(L+1) + m.$$

由于 \(L\) 有 \(d\) 位,适合的归一化方式是减去 \(d-1\)。这样一来,上式就等价于

$$a \le \{j\theta\} \lt b,$$

其中

$$a=\log_{10}L-(d-1), \qquad b=\log_{10}(L+1)-(d-1),$$

\(\{x\}\) 表示 \(x\) 的小数部分。于是“首位数字是否为 \(L\)”被完全转化成了“\(\{j\theta\}\) 是否落在固定半开区间 \([a,b)\) 内”。

Step 2: 把指数序列看成模 1 旋转

定义

$$r_j=\{j\theta\}.$$

那么显然有递推式

$$r_{j+1}=\{r_j+\theta\}, \qquad r_0=0.$$

这意味着我们并不需要真的计算 \(2^j\),只要从 \(0\) 开始不断加上固定常数 \(\theta\),超过 \(1\) 就减去 \(1\),就能依次得到所有 \(\{j\theta\}\)。每当 \(r_j\) 落入区间 \([a,b)\),对应的 \(2^j\) 就以 \(L\) 开头。因此 \(p(L,n)\) 恰好是这个轨道第 \(n\) 次命中目标窗口时的指数。

Step 3: 为什么只看小数部分就足够

\(j\theta\) 的整数部分只负责告诉我们 \(2^j\) 有多少位,而前面的有效数字来自 mantissa,也就是 \(10^{\{j\theta\}}\)。因为

$$2^j = 10^{\lfloor j\theta \rfloor + \{j\theta\}} = 10^{\lfloor j\theta \rfloor}\cdot 10^{\{j\theta\}}.$$

右边的第一个因子只是把小数点向右移动若干位,真正决定首位数字的是第二个因子 \(10^{\{j\theta\}}\in[1,10)\)。对题目里的目标前缀 \(123\),实现中使用的窗口是

$$a=\log_{10}(123)-2 \approx 0.08990511143939792,$$

$$b=\log_{10}(124)-2 \approx 0.09342168516225026.$$

也就是说,只要 \(\{j\theta\}\) 落入这个区间,\(10^{\{j\theta\}}\) 就落在 \([1.23,1.24)\) 内,于是 \(2^j\) 的十进制表示必然以 \(123\) 开头。

Step 4: 用 \(L=12\) 做一个完整示例

为了看清楚这个判定过程,先看较小的前缀 \(L=12\)。这时 \(d=2\),所以

$$a=\log_{10}(12)-1 \approx 0.07918124604762482,$$

$$b=\log_{10}(13)-1 \approx 0.11394335230683678.$$

当 \(j=7\) 时,

$$7\theta \approx 2.1072099696478683,$$

于是

$$\{7\theta\}\approx 0.1072099696478683 \in [a,b).$$

所以算法会判定 \(2^7\) 以 \(12\) 开头,而这确实成立,因为 \(2^7=128\)。

再看 \(j=80\):

$$80\theta \approx 24.082399653118495,$$

因此

$$\{80\theta\}\approx 0.082399653118495 \in [a,b).$$

这说明第二次命中发生在 \(j=80\)。这些小例子正好对应实现里使用的校验点,说明“对数窗口 + 小数部分迭代”的方法与真实的首位数字完全一致。

Step 5: 从示例推广到目标 \(L=123\)

当 \(L\) 改成 \(123\) 时,算法本身没有任何变化,只是窗口端点换成了上面的 \([a,b)\)。接下来只需不断更新

$$r_{j+1}=\{r_j+\theta\}$$

并统计它落入该窗口的次数。第 \(678910\) 次命中对应的指数就是 \(p(123,678910)\)。整个过程中完全不需要构造大整数,也不需要保留任何幂的十进制字符串,因为所有必要信息都压缩在 \(\{j\theta\}\) 里了。

代码如何工作

C++、Python 和 Java 三个实现共享同一套核心思路:先把前缀条件变成两个对数端点,再依次生成 \(\log_{10}2\) 的倍数的小数部分,统计有多少次落进目标区间。

C++ 实现会在运行时根据给定前缀计算位数和区间端点,然后用较高精度的浮点数逐个推进指数,同时先验证几个小检查点,例如 \(p(12,1)=7\)、\(p(12,2)=80\) 以及 \(p(123,45)=12710\)。Python 实现使用同样的浮点递推,只是直接针对最终问题 \(L=123\)、\(n=678910\) 进行求解。

Java 实现则更进一步地针对这个固定输入做了特化。它把 \(\log_{10}2\) 以及前缀 \(123\) 对应的两个端点都预先写成按 \(10^{18}\) 放大的整数,从而用纯整数运算模拟“加上 \(\theta\) 再对 1 取模”的过程。由于前缀 \(123\) 的上端点小于 \(\log_{10}2\),一次命中只能出现在发生回绕的那一步之后,所以这份整数实现只需要在回绕时检查区间命中情况即可。

复杂度分析

每次迭代都只包含常数次加法、比较以及至多一次“减去 1”的回绕修正,因此单步代价是 \(O(1)\)。总时间复杂度就是 \(O(p(L,n))\),因为算法会从小到大检查指数,直到找到第 \(n\) 次命中。空间复杂度为 \(O(1)\),整个过程中只维护少量标量状态。

注释与参考资料

  1. Project Euler 题目页面:https://projecteuler.net/problem=686
  2. 常用对数:Wikipedia - Common logarithm
  3. 小数部分:Wikipedia - Fractional part
  4. 均匀分布定理:Wikipedia - Equidistribution theorem
  5. 本福特定律:Wikipedia - Benford's law

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

Для десятичного префикса \(L\) величина \(p(L,n)\) означает показатель \(j\) у \(n\)-й степени двойки, десятичная запись которой начинается ровно с цифр \(L\). В задаче 686 требуется найти \(p(123,678910)\).

Полностью вычислять огромные числа \(2^j\) не нужно. Ведущие цифры определяются только дробной частью десятичного логарифма, поэтому задача превращается в подсчет попаданий орбиты в фиксированное окно внутри \([0,1)\).

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

Обозначим

$$\theta=\log_{10}2.$$

Тогда десятичная форма \(2^j\) контролируется величиной \(j\theta\).

Step 1: Превращаем условие на префикс в логарифмическое окно

Пусть число \(L\) состоит из \(d\) цифр. Условие «\(2^j\) начинается с \(L\)» означает, что существует целое \(m\), для которого

$$L\cdot 10^m \le 2^j \lt (L+1)\cdot 10^m.$$

После применения \(\log_{10}\) получаем

$$\log_{10}L + m \le j\theta \lt \log_{10}(L+1) + m.$$

Так как у \(L\) ровно \(d\) цифр, удобно вычесть \(d-1\). Тогда условие эквивалентно

$$a \le \{j\theta\} \lt b,$$

где

$$a=\log_{10}L-(d-1), \qquad b=\log_{10}(L+1)-(d-1),$$

а \(\{x\}\) обозначает дробную часть \(x\). Значит, вопрос о ведущих цифрах сводится к проверке попадания дробной части \(\{j\theta\}\) в одно фиксированное полуоткрытое окно.

Step 2: Смотрим на последовательность как на вращение по модулю 1

Определим

$$r_j=\{j\theta\}.$$

Тогда автоматически

$$r_{j+1}=\{r_j+\theta\}, \qquad r_0=0.$$

Это означает, что на каждом шаге мы сдвигаемся на одну и ту же величину \(\theta\) по единичному интервалу. Когда \(r_j\) попадает в \([a,b)\), число \(2^j\) начинается с префикса \(L\). Следовательно, \(p(L,n)\) есть просто индекс \(n\)-го попадания этой орбиты в целевое окно.

Step 3: Почему достаточно дробной части

Целая часть \(j\theta\) отвечает только за число цифр в \(2^j\). Первые цифры определяются мантиссой \(10^{\{j\theta\}}\). Действительно,

$$2^j = 10^{\lfloor j\theta \rfloor + \{j\theta\}} = 10^{\lfloor j\theta \rfloor}\cdot 10^{\{j\theta\}}.$$

Первый множитель лишь сдвигает десятичную точку, а нужный префикс содержится во втором множителе \(10^{\{j\theta\}}\in[1,10)\). Для целевого префикса \(123\) реализации используют окно

$$a=\log_{10}(123)-2 \approx 0.08990511143939792,$$

$$b=\log_{10}(124)-2 \approx 0.09342168516225026.$$

Если \(\{j\theta\}\) лежит внутри этого интервала, то мантисса попадает в \([1.23,1.24)\), а значит десятичная запись \(2^j\) начинается именно с \(123\).

Step 4: Разобранный пример для \(L=12\)

Возьмем более короткий префикс \(L=12\). Тогда \(d=2\), и

$$a=\log_{10}(12)-1 \approx 0.07918124604762482,$$

$$b=\log_{10}(13)-1 \approx 0.11394335230683678.$$

При \(j=7\)

$$7\theta \approx 2.1072099696478683,$$

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

$$\{7\theta\}\approx 0.1072099696478683 \in [a,b).$$

Это предсказывает, что \(2^7\) начинается с \(12\), и действительно \(2^7=128\).

При \(j=80\)

$$80\theta \approx 24.082399653118495,$$

откуда

$$\{80\theta\}\approx 0.082399653118495 \in [a,b).$$

Значит, второе попадание происходит при \(j=80\). Эти же контрольные значения используются в реализации.

Step 5: Переход к целевому запросу

Для \(L=123\) меняются только границы окна. Мы продолжаем итерацию

$$r_{j+1}=\{r_j+\theta\}$$

и считаем, сколько раз орбита входит в интервал для префикса \(123\). Индекс \(678910\)-го такого попадания и есть искомое значение \(p(123,678910)\). Никакие длинные целые числа хранить не нужно: всю важную информацию уже несет дробная часть логарифма.

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

Реализации на C++, Python и Java используют одну и ту же идею: сначала из префикса строятся две логарифмические границы, затем последовательно генерируются дробные части кратных \(\log_{10}2\), и каждое попадание в целевое окно увеличивает счетчик.

Реализация на C++ вычисляет число цифр и границы окна во время выполнения, после чего проходит показатели по одному, используя повышенную точность вещественных вычислений. Перед выводом окончательного ответа она также проверяет небольшие контрольные случаи \(p(12,1)=7\), \(p(12,2)=80\) и \(p(123,45)=12710\).

Реализация на Python применяет ту же вещественную рекурсию, но уже напрямую для входа \(L=123\), \(n=678910\). Реализация на Java еще более специализирована: она хранит \(\log_{10}2\) и обе границы окна для \(123\) как целые числа, масштабированные множителем \(10^{18}\). Поскольку верхняя граница окна для \(123\) меньше, чем \(\log_{10}2\), успешное попадание возможно только сразу после перехода через \(1\); поэтому целочисленная версия проверяет только такие шаги с wraparound.

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

Каждая итерация требует лишь постоянного числа сложений, сравнений и не более одной операции приведения обратно в \([0,1)\). Поэтому время работы равно \(O(p(L,n))\): алгоритм перебирает показатели, пока не дойдет до нужного попадания. Память равна \(O(1)\), так как поддерживается только несколько скалярных величин.

Примечания и источники

  1. Страница задачи: https://projecteuler.net/problem=686
  2. Десятичный логарифм: Wikipedia - Common logarithm
  3. Дробная часть: Wikipedia - Fractional part
  4. Теорема о равномерном распределении: Wikipedia - Equidistribution theorem
  5. Закон Бенфорда: Wikipedia - Benford's law

ملخص المشكلة

لأي بادئة عشرية \(L\)، نرمز بـ \(p(L,n)\) إلى الأس \(j\) الذي تمثل عنده القوة \(2^j\) الحالة رقم \(n\) التي تبدأ فيها الكتابة العشرية بالبادئة \(L\) بالضبط. في المسألة 686 نريد حساب \(p(123,678910)\).

لا حاجة إلى تكوين أعداد هائلة مثل \(2^j\) مباشرة. الفكرة الحاسمة هي أن الأرقام الأولى تعتمد فقط على الجزء الكسري من اللوغاريتم العشري، ولذلك تتحول المسألة إلى عد مرات دخول متتالية معينة إلى نافذة ثابتة داخل \([0,1)\).

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

لنضع

$$\theta=\log_{10}2.$$

عندئذ تصبح البنية العشرية للعدد \(2^j\) محكومة بالقيمة \(j\theta\).

Step 1: تحويل شرط البادئة إلى نافذة لوغاريتمية

إذا كان للعدد \(L\) عدد أرقام يساوي \(d\)، فإن عبارة «\(2^j\) يبدأ بـ \(L\)» تعني وجود عدد صحيح \(m\) بحيث

$$L\cdot 10^m \le 2^j \lt (L+1)\cdot 10^m.$$

وبأخذ \(\log_{10}\) نحصل على

$$\log_{10}L + m \le j\theta \lt \log_{10}(L+1) + m.$$

ولأن \(L\) يتكون من \(d\) خانات، فإن التطبيع الطبيعي يكون بطرح \(d-1\). وهكذا يصبح الشرط مكافئًا لـ

$$a \le \{j\theta\} \lt b,$$

حيث

$$a=\log_{10}L-(d-1), \qquad b=\log_{10}(L+1)-(d-1),$$

والرمز \(\{x\}\) يعني الجزء الكسري من \(x\). بهذا نكون قد استبدلنا سؤالًا عن أوائل الأرقام باختبار بسيط: هل يقع \(\{j\theta\}\) داخل مجال نصف مفتوح ثابت أم لا.

Step 2: النظر إلى المسألة كدوران بترديد 1

نعرّف

$$r_j=\{j\theta\}.$$

ومن ثم

$$r_{j+1}=\{r_j+\theta\}, \qquad r_0=0.$$

هذا يعني أننا نتحرك في كل خطوة بالمقدار نفسه \(\theta\) على الفترة الواحدة. كل مرة يقع فيها \(r_j\) داخل \([a,b)\)، تكون القوة \(2^j\) بادئتها العشرية هي \(L\). لذلك فإن \(p(L,n)\) هو ببساطة فهرس الزيارة رقم \(n\) لهذه النافذة.

Step 3: لماذا يكفي الجزء الكسري فقط

الجزء الصحيح من \(j\theta\) يخبرنا فقط بعدد خانات \(2^j\)، أما الأرقام الأولى نفسها فتأتي من المانتيسا \(10^{\{j\theta\}}\). ذلك لأن

$$2^j = 10^{\lfloor j\theta \rfloor + \{j\theta\}} = 10^{\lfloor j\theta \rfloor}\cdot 10^{\{j\theta\}}.$$

العامل الأول لا يفعل أكثر من تحريك الفاصلة العشرية، بينما العامل الثاني \(10^{\{j\theta\}}\in[1,10)\) هو الذي يحدد البادئة. بالنسبة إلى البادئة الهدف \(123\)، فإن النافذة المستخدمة في التطبيقات هي

$$a=\log_{10}(123)-2 \approx 0.08990511143939792,$$

$$b=\log_{10}(124)-2 \approx 0.09342168516225026.$$

فإذا وقع \(\{j\theta\}\) داخل هذا المجال، أصبحت المانتيسا في \([1.23,1.24)\)، وهذا يعني مباشرة أن الكتابة العشرية لـ \(2^j\) تبدأ بـ \(123\).

Step 4: مثال محلول عندما \(L=12\)

لنأخذ بادئة أصغر هي \(L=12\). عندها \(d=2\)، وبالتالي

$$a=\log_{10}(12)-1 \approx 0.07918124604762482,$$

$$b=\log_{10}(13)-1 \approx 0.11394335230683678.$$

عندما \(j=7\)، نجد

$$7\theta \approx 2.1072099696478683,$$

ومن ثم

$$\{7\theta\}\approx 0.1072099696478683 \in [a,b).$$

إذن تتنبأ الطريقة بأن \(2^7\) يبدأ بـ \(12\)، وهذا صحيح لأن \(2^7=128\).

وعندما \(j=80\)،

$$80\theta \approx 24.082399653118495,$$

فتكون

$$\{80\theta\}\approx 0.082399653118495 \in [a,b).$$

لذلك تأتي المطابقة الثانية عند \(j=80\). هذه القيم الصغيرة نفسها تُستخدم كنقاط تحقق في التطبيق.

Step 5: تطبيق الفكرة نفسها على الاستعلام الهدف

عند الانتقال إلى \(L=123\) لا تتغير الخوارزمية، بل تتغير فقط حدود النافذة. نستمر في تحديث

$$r_{j+1}=\{r_j+\theta\}$$

ونعد عدد مرات الدخول إلى نافذة \(123\). وعند الزيارة رقم \(678910\) يكون الأس المقابل هو \(p(123,678910)\). لا توجد أي حاجة إلى التعامل مع أعداد صحيحة ضخمة، لأن نافذة اللوغاريتم تعطي معلومات البداية العشرية كاملة.

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

تتبع تطبيقات C++ وPython وJava الخطة الرياضية نفسها: تحويل شرط البادئة إلى حدين لوغاريتميين، ثم توليد الأجزاء الكسرية للمضاعفات المتتالية لـ \(\log_{10}2\)، ثم زيادة العداد كلما دخلت القيمة في المجال المطلوب.

تنفذ نسخة C++ الحساب العام للبادئة المطلوبة أثناء التشغيل، وتستخدم دقة عددية أعلى في الحسابات العشرية، كما تتحقق قبل إخراج النتيجة من حالات صغيرة مثل \(p(12,1)=7\) و\(p(12,2)=80\) و\(p(123,45)=12710\). أما نسخة Python فتستعمل التكرار نفسه بالكسور العشرية ولكنها مخصصة مباشرة للحالة \(L=123\)، \(n=678910\).

نسخة Java أكثر تخصيصًا للحالة النهائية: فهي تخزن \(\log_{10}2\) وحدي النافذة الخاصة بالبادئة \(123\) على هيئة أعداد صحيحة بعد تكبيرها بمقياس \(10^{18}\). وبما أن الحد العلوي لنافذة \(123\) أصغر من \(\log_{10}2\)، فإن كل إصابة لا بد أن تحدث مباشرة بعد خطوة التفاف حول \(1\)، ولذلك تكتفي النسخة الصحيحة بفحص الخطوات التي يحدث فيها هذا الالتفاف.

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

كل دورة من الخوارزمية تنفذ عددًا ثابتًا من عمليات الجمع والمقارنة، ومعها على الأكثر عملية واحدة لإرجاع القيمة إلى المجال \([0,1)\). لذا فإن زمن التنفيذ هو \(O(p(L,n))\)، لأننا نمر على الأسس حتى نصل إلى الضربة المطلوبة. أما الذاكرة فهي \(O(1)\)، لأن الحالة كلها تختزل في بضعة مقادير عددية فقط.

ملاحظات ومراجع

  1. صفحة المسألة: https://projecteuler.net/problem=686
  2. اللوغاريتم العشري: Wikipedia - Common logarithm
  3. الجزء الكسري: Wikipedia - Fractional part
  4. مبرهنة التوزيع المتساوي: Wikipedia - Equidistribution theorem
  5. قانون بنفورد: Wikipedia - Benford's law