Let \(S(n)\) denote the number of integers
$$0 \le x < 10^n$$
whose decimal digit sum is \(23\) and which are divisible by \(23\). Leading zeros are therefore allowed in the \(n\)-digit representation, which matches the code. The Project Euler target is \(S(11^{12}) \bmod 10^9\).
Write
$$x = \sum_{i=0}^{n-1} d_i 10^i,\qquad 0 \le d_i \le 9,$$
where \(d_0\) is the units digit. Then the problem imposes two simultaneous constraints:
$$\sum_{i=0}^{n-1} d_i = 23,$$
$$\sum_{i=0}^{n-1} d_i 10^i \equiv 0 \pmod{23}.$$
A direct dynamic program over all \(n\) positions would be impossible for \(n = 11^{12}\), so the key is to compress positions that behave identically modulo \(23\).
Modulo \(23\), powers of \(10\) repeat with period \(22\), because
$$10^{22} \equiv 1 \pmod{23}.$$
Therefore, if two positions satisfy \(i \equiv j \pmod{22}\), then
$$10^i \equiv 10^j \pmod{23}.$$
So all digits sitting in the same residue class modulo \(22\) contribute to divisibility by the same weight. Define
$$w_c = 10^c \bmod 23,\qquad c=0,1,\ldots,21.$$
If \(n = 22q + r\) with \(0 \le r < 22\), then class \(c\) contains
$$u_c = \begin{cases} q+1, & c < r, \\ q, & c \ge r. \end{cases}$$
For example, if \(n=25\), then \(q=1\), \(r=3\), so classes \(0,1,2\) contain two positions each, while the other \(19\) classes contain one position each.
Inside one class \(c\), suppose the digits in that class add up to \(k\). Then their total contribution modulo \(23\) is simply
$$k\,w_c \pmod{23},$$
because every position in that class carries the same weight \(w_c\). So we do not need the full digit pattern of the class; we only need to know how many assignments produce class sum \(k\).
For a class of size \(u\), the generating polynomial is
$$G_u(x) = (1+x+x^2+\cdots+x^9)^u.$$
The coefficient \([x^k]G_u(x)\) is exactly the number of ways to place \(u\) digits from \(0\) to \(9\) whose sum is \(k\).
Because the global digit sum is only \(23\), coefficients above degree \(23\) can never matter. So the code truncates every polynomial to degree \(23\), turning the huge exponent \(u\) into a manageable binary-exponentiation problem.
Every class size is either \(q\) or \(q+1\). Therefore we only need the two truncated polynomials
$$G_q(x) = (1+x+\cdots+x^9)^q,$$
$$G_{q+1}(x) = (1+x+\cdots+x^9)^{q+1},$$
both computed modulo \(10^9\). The function poly_pow_digit_sum does this by repeated squaring, and the multiplication routine discards all terms above \(x^{23}\).
After compressing each residue class into “choose a class sum \(k\) with multiplicity \([x^k]G_{u_c}(x)\)”, the problem becomes a 22-step dynamic program. Let
$$DP[s][t]$$
be the number of ways, after processing some classes, to obtain total digit sum \(s\) and remainder \(t \pmod{23}\).
When processing class \(c\), choosing class sum \(k\) produces the transition
$$s' = s+k,\qquad t' = (t + w_c k) \bmod 23.$$
The multiplicity of this transition is \([x^k]G_{u_c}(x)\). After all \(22\) classes have been processed, the desired count is
$$DP[23][0].$$
This is the whole reason the huge value of \(n\) becomes harmless: the algorithm never iterates over all positions, only over the \(22\) residue classes.
The implementation verifies itself with three checkpoints:
$$S(9)=263626,$$
$$S(42)=6377168878570056,$$
and a consistency check that the compressed class DP matches a direct position-by-position DP for
$$n=200.$$
The direct DP is only feasible for moderate \(n\), but it is an excellent validation tool: it confirms that grouping positions by residue modulo \(22\) loses no information.
poly_mul_mod multiplies two digit-sum polynomials and truncates to degree \(23\). poly_pow_digit_sum raises \(1+x+\cdots+x^9\) to exponent \(q\) or \(q+1\) by binary exponentiation. count_via_classes_mod builds the \(22\) modular weights \(w_c\), runs the DP over \((\text{digit sum}, \text{remainder})\), and returns \(DP[23][0]\). The functions direct_count_exact and direct_count_mod are only for checkpoints on smaller \(n\).
Polynomial multiplication is on vectors of length \(24\), so one multiplication costs \(O(23^2)\). Binary exponentiation therefore costs
$$O(23^2 \log n).$$
The class DP visits \(22\) classes, \(24\) digit-sum states, \(23\) remainders, and up to \(24\) class-sum choices, so its cost is
$$O(22 \cdot 23^3),$$
which is effectively constant for this fixed target sum. Memory usage is \(O(23^2)\).
Sei \(S(n)\) die Anzahl der ganzen Zahlen
$$0 \le x < 10^n,$$
deren Dezimalziffernsumme \(23\) ist und die durch \(23\) teilbar sind. Führende Nullen sind in der \(n\)-stelligen Darstellung also erlaubt; genau das zählt der Code. Gesucht ist \(S(11^{12}) \bmod 10^9\).
Schreibe
$$x = \sum_{i=0}^{n-1} d_i 10^i,\qquad 0 \le d_i \le 9,$$
wobei \(d_0\) die Einerziffer ist. Dann lauten die beiden Bedingungen:
$$\sum_{i=0}^{n-1} d_i = 23,$$
$$\sum_{i=0}^{n-1} d_i 10^i \equiv 0 \pmod{23}.$$
Ein direktes DP über alle \(n\) Positionen wäre für \(n = 11^{12}\) aussichtslos. Deshalb müssen Stellen zusammengefasst werden, die modulo \(23\) identisch wirken.
Modulo \(23\) haben die Zehnerpotenzen Periode \(22\), denn
$$10^{22} \equiv 1 \pmod{23}.$$
Also gilt für \(i \equiv j \pmod{22}\) sofort
$$10^i \equiv 10^j \pmod{23}.$$
Alle Ziffern in derselben Restklasse modulo \(22\) tragen daher mit demselben Gewicht zur Teilbarkeit bei. Definiere
$$w_c = 10^c \bmod 23,\qquad c=0,1,\ldots,21.$$
Schreibt man \(n = 22q + r\) mit \(0 \le r < 22\), dann enthält Klasse \(c\)
$$u_c = \begin{cases} q+1, & c < r, \\ q, & c \ge r. \end{cases}$$
Beispiel: Für \(n=25\) ist \(q=1\), \(r=3\). Also haben die Klassen \(0,1,2\) jeweils zwei Positionen, die übrigen \(19\) Klassen je eine Position.
Angenommen, die Ziffern einer Klasse \(c\) summieren sich zu \(k\). Dann ist ihr Gesamtbeitrag modulo \(23\) einfach
$$k\,w_c \pmod{23},$$
weil alle Stellen dieser Klasse dasselbe Gewicht \(w_c\) besitzen. Das genaue Muster der Ziffern innerhalb der Klasse ist also unnötig; es genügt zu wissen, wie viele Belegungen die Klassensumme \(k\) erzeugen.
Für eine Klasse der Größe \(u\) ist das erzeugende Polynom
$$G_u(x) = (1+x+x^2+\cdots+x^9)^u.$$
Der Koeffizient \([x^k]G_u(x)\) zählt genau die Belegungen der \(u\) Stellen mit Ziffern \(0\) bis \(9\), deren Summe \(k\) ist.
Da die globale Ziffernsumme nur \(23\) beträgt, können Koeffizienten oberhalb des Grades \(23\) nie eine Rolle spielen. Deshalb schneidet der Code alle Polynome nach \(x^{23}\) ab und macht selbst riesige Exponenten durch binäre Exponentiation handhabbar.
Jede Klassenlänge ist entweder \(q\) oder \(q+1\). Also genügen die zwei abgeschnittenen Polynome
$$G_q(x) = (1+x+\cdots+x^9)^q,$$
$$G_{q+1}(x) = (1+x+\cdots+x^9)^{q+1},$$
jeweils modulo \(10^9\). Die Funktion poly_pow_digit_sum berechnet sie per Exponentiation durch Quadrieren, und die Multiplikation verwirft alle Terme oberhalb von \(x^{23}\).
Nachdem jede Restklasse auf „wähle eine Klassensumme \(k\) mit Vielfachheit \([x^k]G_{u_c}(x)\)“ reduziert wurde, bleibt ein 22-stufiges DP. Definiere
$$DP[s][t]$$
als Anzahl der Möglichkeiten, nach einigen Klassen die Gesamtziffernsumme \(s\) und den Rest \(t \pmod{23}\) zu erreichen.
Bei der Verarbeitung der Klasse \(c\) führt die Wahl der Klassensumme \(k\) zum Übergang
$$s' = s+k,\qquad t' = (t + w_c k) \bmod 23.$$
Die Vielfachheit dieses Übergangs ist \([x^k]G_{u_c}(x)\). Nach allen \(22\) Klassen ist die gesuchte Anzahl
$$DP[23][0].$$
Genau dadurch wird das gewaltige \(n\) unschädlich: Der Algorithmus iteriert nie über alle Stellen, sondern nur über die \(22\) Restklassen.
Die Implementierung kontrolliert sich mit drei Prüfungen:
$$S(9)=263626,$$
$$S(42)=6377168878570056,$$
sowie einem Konsistenztest, dass das Klassen-DP mit einem direkten Stellen-DP übereinstimmt für
$$n=200.$$
Das direkte DP ist nur für moderate \(n\) machbar, aber als Validierung sehr stark: Es bestätigt, dass die Gruppierung nach Restklassen modulo \(22\) keinerlei Information verliert.
poly_mul_mod multipliziert zwei Ziffernsummen-Polynome und schneidet bei Grad \(23\) ab. poly_pow_digit_sum potenziert \(1+x+\cdots+x^9\) auf den Exponenten \(q\) oder \(q+1\). count_via_classes_mod erzeugt die \(22\) Gewichte \(w_c\), führt das DP über \((\text{Ziffernsumme}, \text{Rest})\) aus und liefert \(DP[23][0]\). Die Funktionen direct_count_exact und direct_count_mod werden nur für Kontrolltests bei kleineren \(n\) verwendet.
Polynom-Multiplikation arbeitet auf Vektoren der Länge \(24\), also kostet eine Multiplikation \(O(23^2)\). Binäre Exponentiation benötigt damit
$$O(23^2 \log n).$$
Das Klassen-DP durchläuft \(22\) Klassen, \(24\) Ziffernsummen, \(23\) Reste und bis zu \(24\) Klassensummenwerte, also
$$O(22 \cdot 23^3),$$
praktisch konstant für die feste Zielsumme \(23\). Der Speicherbedarf ist \(O(23^2)\).
\(S(n)\),
$$0 \le x < 10^n$$
koşulunu sağlayan, basamak toplamı \(23\) olan ve \(23\)'e tam bölünen tam sayıların sayısıdır. Yani \(n\) basamaklı yazımda baştaki sıfırlara izin verilir; kodun saydığı nesne tam olarak budur. Project Euler hedefi \(S(11^{12}) \bmod 10^9\)'dur.
Şu şekilde yazalım:
$$x = \sum_{i=0}^{n-1} d_i 10^i,\qquad 0 \le d_i \le 9,$$
burada \(d_0\) birler basamağıdır. O zaman problem iki eşzamanlı koşul ister:
$$\sum_{i=0}^{n-1} d_i = 23,$$
$$\sum_{i=0}^{n-1} d_i 10^i \equiv 0 \pmod{23}.$$
\(n = 11^{12}\) kadar büyükken tüm pozisyonlar üzerinde doğrudan bir DP kurmak imkansızdır. Kritik fikir, mod \(23\) açısından aynı davranan pozisyonları tek sınıfta toplamaktır.
\(23\) modunda \(10\)'un kuvvetleri \(22\) periyotludur; çünkü
$$10^{22} \equiv 1 \pmod{23}.$$
Bu yüzden \(i \equiv j \pmod{22}\) ise
$$10^i \equiv 10^j \pmod{23}$$
olur. Yani mod \(22\)'de aynı sınıfa düşen tüm pozisyonlar, bölünebilirlik hesabına aynı ağırlıkla katkı verir. Şunu tanımlayalım:
$$w_c = 10^c \bmod 23,\qquad c=0,1,\ldots,21.$$
\(n = 22q + r\), \(0 \le r < 22\) yazarsak sınıf \(c\)'deki pozisyon sayısı
$$u_c = \begin{cases} q+1, & c < r, \\ q, & c \ge r. \end{cases}$$
Örneğin \(n=25\) için \(q=1\), \(r=3\) olur; dolayısıyla \(0,1,2\) sınıflarında iki pozisyon, kalan \(19\) sınıfta birer pozisyon vardır.
Bir sınıf \(c\) içindeki rakamların toplamı \(k\) olsun. O sınıfın mod \(23\) katkısı doğrudan
$$k\,w_c \pmod{23}$$
olur; çünkü o sınıftaki bütün pozisyonlar aynı \(w_c\) ağırlığına sahiptir. Demek ki sınıf içindeki rakamların tam dizilimi gerekmez; yalnızca sınıf toplamı \(k\)'yi kaç farklı şekilde elde ettiğimizi bilmek yeterlidir.
Boyutu \(u\) olan bir sınıf için üreteç polinomu
$$G_u(x) = (1+x+x^2+\cdots+x^9)^u$$
şeklindedir. Buradaki \([x^k]G_u(x)\) katsayısı, \(u\) adet \(0\) ile \(9\) arasındaki rakamın toplamını \(k\) yapan atama sayısını verir.
Toplam basamak toplamı sadece \(23\) olduğu için \(23\)'ten büyük dereceli terimler hiçbir zaman kullanılamaz. Bu yüzden kod tüm polinomları \(x^{23}\)'te keser; böylece dev üsler bile ikili üs alma ile yönetilebilir hale gelir.
Her sınıfın boyu ya \(q\) ya da \(q+1\)'dir. Dolayısıyla yalnızca şu iki kesilmiş polinomu hesaplamak yeterlidir:
$$G_q(x) = (1+x+\cdots+x^9)^q,$$
$$G_{q+1}(x) = (1+x+\cdots+x^9)^{q+1}.$$
Her ikisi de \(10^9\) modunda tutulur. poly_pow_digit_sum bunu repeated squaring ile yapar; çarpım fonksiyonu \(x^{23}\)'ten sonraki tüm terimleri anında atar.
Her sınıfı “\([x^k]G_{u_c}(x)\) kadar çoklukla sınıf toplamı \(k\) seç” problemine indirdikten sonra geriye 22 adımlık bir DP kalır. Şunu tanımlayalım:
$$DP[s][t]$$
işlenmiş bazı sınıflardan sonra toplam basamak toplamı \(s\) ve mod \(23\) kalanı \(t\) olan atama sayısı olsun.
Sınıf \(c\) işlenirken sınıf toplamı \(k\) seçilirse geçiş
$$s' = s+k,\qquad t' = (t + w_c k) \bmod 23$$
şeklindedir. Bu geçişin çarpanı da \([x^k]G_{u_c}(x)\)'dir. Bütün \(22\) sınıf işlendiğinde aradığımız sayı
$$DP[23][0]$$
olur. Böylece \(n\)'in devasa olması sorun olmaktan çıkar; algoritma tüm pozisyonları değil, yalnızca \(22\) artık sınıfını işler.
Uygulama kendini üç kontrolle doğrular:
$$S(9)=263626,$$
$$S(42)=6377168878570056,$$
ve ayrıca sıkıştırılmış sınıf DP'sinin doğrudan pozisyon-pozisyon DP ile
$$n=200$$
için aynı sonucu verdiği kontrol edilir. Doğrudan DP ancak orta ölçekli \(n\)'lerde mümkün olsa da, bu karşılaştırma mod \(22\) sınıflamasının bilgi kaybetmediğini çok güçlü biçimde doğrular.
poly_mul_mod iki basamak-toplam polinomunu çarpar ve sonucu derece \(23\)'te keser. poly_pow_digit_sum, \(1+x+\cdots+x^9\) polinomunu \(q\) veya \(q+1\) kuvvetine çıkarır. count_via_classes_mod \(22\) ağırlığı \(w_c\) üretir, \((\text{basamak toplamı}, \text{kalan})\) DP'sini çalıştırır ve \(DP[23][0]\)'ı döndürür. direct_count_exact ile direct_count_mod ise yalnızca küçük kontrol örnekleri içindir.
Polinom çarpımı uzunluğu \(24\) olan diziler üzerinde yapıldığı için tek çarpım maliyeti \(O(23^2)\)'dir. Dolayısıyla ikili üs alma
$$O(23^2 \log n)$$
zaman alır. Sınıf DP'si \(22\) sınıf, \(24\) basamak toplamı, \(23\) kalan ve en fazla \(24\) sınıf-toplam seçeneği dolaşır; bu da
$$O(22 \cdot 23^3)$$
maliyet verir. Hedef toplam \(23\) sabit olduğundan bu pratikte sabit sayılır. Bellek kullanımı \(O(23^2)\)'dir.
Sea \(S(n)\) el número de enteros
$$0 \le x < 10^n$$
cuyo sumatorio de dígitos es \(23\) y que además son divisibles por \(23\). Por tanto, en la representación de longitud \(n\) se permiten ceros iniciales; eso coincide exactamente con el código. El objetivo de Project Euler es \(S(11^{12}) \bmod 10^9\).
Escribimos
$$x = \sum_{i=0}^{n-1} d_i 10^i,\qquad 0 \le d_i \le 9,$$
donde \(d_0\) es la cifra de unidades. Entonces las restricciones son
$$\sum_{i=0}^{n-1} d_i = 23,$$
$$\sum_{i=0}^{n-1} d_i 10^i \equiv 0 \pmod{23}.$$
Un DP directo sobre las \(n\) posiciones sería imposible para \(n = 11^{12}\), así que hay que comprimir posiciones que se comportan igual módulo \(23\).
Módulo \(23\), las potencias de \(10\) tienen periodo \(22\), ya que
$$10^{22} \equiv 1 \pmod{23}.$$
Por tanto, si \(i \equiv j \pmod{22}\), entonces
$$10^i \equiv 10^j \pmod{23}.$$
Todas las cifras situadas en la misma clase residual módulo \(22\) aportan al criterio de divisibilidad con el mismo peso. Definimos
$$w_c = 10^c \bmod 23,\qquad c=0,1,\ldots,21.$$
Si \(n = 22q + r\) con \(0 \le r < 22\), la clase \(c\) contiene
$$u_c = \begin{cases} q+1, & c < r, \\ q, & c \ge r. \end{cases}$$
Por ejemplo, si \(n=25\), entonces \(q=1\), \(r=3\): las clases \(0,1,2\) tienen dos posiciones y las otras \(19\) tienen una.
Dentro de una clase \(c\), supongamos que la suma de sus dígitos es \(k\). Entonces su contribución total módulo \(23\) es simplemente
$$k\,w_c \pmod{23},$$
porque todas las posiciones de esa clase tienen el mismo peso \(w_c\). Así, no hace falta conocer el patrón exacto de dígitos; basta con saber cuántas asignaciones producen suma de clase \(k\).
Para una clase de tamaño \(u\), el polinomio generador es
$$G_u(x) = (1+x+x^2+\cdots+x^9)^u.$$
El coeficiente \([x^k]G_u(x)\) cuenta exactamente cuántas formas hay de asignar \(u\) dígitos entre \(0\) y \(9\) cuya suma sea \(k\).
Como la suma total de dígitos es solo \(23\), los coeficientes por encima del grado \(23\) nunca importan. Por eso el código trunca todos los polinomios en \(x^{23}\), convirtiendo exponentes gigantes en un problema manejable de exponenciación binaria.
Cada clase tiene tamaño \(q\) o \(q+1\). Por eso basta calcular los dos polinomios truncados
$$G_q(x) = (1+x+\cdots+x^9)^q,$$
$$G_{q+1}(x) = (1+x+\cdots+x^9)^{q+1},$$
ambos módulo \(10^9\). La función poly_pow_digit_sum los obtiene mediante exponenciación rápida, y la multiplicación descarta cualquier término de grado mayor que \(23\).
Una vez comprimida cada clase a “elige una suma de clase \(k\) con multiplicidad \([x^k]G_{u_c}(x)\)”, queda un DP de 22 pasos. Definimos
$$DP[s][t]$$
como el número de maneras de obtener, tras procesar algunas clases, suma total de dígitos \(s\) y residuo \(t \pmod{23}\).
Al procesar la clase \(c\), elegir suma de clase \(k\) produce la transición
$$s' = s+k,\qquad t' = (t + w_c k) \bmod 23.$$
La multiplicidad es \([x^k]G_{u_c}(x)\). Después de procesar las \(22\) clases, el valor buscado es
$$DP[23][0].$$
Esa es la razón por la que el valor inmenso de \(n\) deja de ser un problema: el algoritmo no recorre todas las posiciones, solo las \(22\) clases residuales.
La implementación se valida con tres controles:
$$S(9)=263626,$$
$$S(42)=6377168878570056,$$
y además una verificación de consistencia de que el DP comprimido por clases coincide con un DP directo posición por posición para
$$n=200.$$
El DP directo solo es viable para \(n\) moderados, pero como herramienta de validación es excelente: confirma que agrupar posiciones por residuo módulo \(22\) no pierde ninguna información.
poly_mul_mod multiplica dos polinomios de suma de dígitos y trunca en grado \(23\). poly_pow_digit_sum eleva \(1+x+\cdots+x^9\) al exponente \(q\) o \(q+1\). count_via_classes_mod construye los \(22\) pesos \(w_c\), ejecuta el DP sobre \((\text{suma de dígitos}, \text{residuo})\) y devuelve \(DP[23][0]\). Las funciones direct_count_exact y direct_count_mod solo se usan para checkpoints con \(n\) pequeños.
La multiplicación polinómica trabaja con vectores de longitud \(24\), así que una multiplicación cuesta \(O(23^2)\). La exponenciación rápida cuesta entonces
$$O(23^2 \log n).$$
El DP por clases recorre \(22\) clases, \(24\) estados de suma, \(23\) residuos y hasta \(24\) opciones de suma de clase, es decir
$$O(22 \cdot 23^3),$$
lo que es prácticamente constante para esta suma objetivo fija. La memoria usada es \(O(23^2)\).
记 \(S(n)\) 为满足
$$0 \le x < 10^n$$
且“十进制数位和等于 \(23\)、同时能被 \(23\) 整除”的整数个数。也就是说,在长度为 \(n\) 的十进制表示中允许前导零,这与代码的计数方式完全一致。Project Euler 目标是求 \(S(11^{12}) \bmod 10^9\)。
设
$$x = \sum_{i=0}^{n-1} d_i 10^i,\qquad 0 \le d_i \le 9,$$
其中 \(d_0\) 是个位数字。那么题目要求同时满足
$$\sum_{i=0}^{n-1} d_i = 23,$$
$$\sum_{i=0}^{n-1} d_i 10^i \equiv 0 \pmod{23}.$$
如果对全部 \(n\) 个位置直接做 DP,那么 \(n = 11^{12}\) 时完全不可行。关键是把在模 \(23\) 意义下作用相同的位置压缩到一起。
在模 \(23\) 下,\(10\) 的幂以 \(22\) 为周期,因为
$$10^{22} \equiv 1 \pmod{23}.$$
因此只要 \(i \equiv j \pmod{22}\),就有
$$10^i \equiv 10^j \pmod{23}.$$
这说明所有下标同余于同一个 \(c\) 的位置,在整除条件中都有相同的权重。定义
$$w_c = 10^c \bmod 23,\qquad c=0,1,\ldots,21.$$
若 \(n = 22q + r\),其中 \(0 \le r < 22\),则第 \(c\) 类的位置个数为
$$u_c = \begin{cases} q+1, & c < r, \\ q, & c \ge r. \end{cases}$$
例如 \(n=25\) 时,\(q=1\)、\(r=3\),于是类 \(0,1,2\) 各有两个位置,其余 \(19\) 类各有一个位置。
设某一类 \(c\) 中所有数字之和为 \(k\)。那么这整类对模 \(23\) 的贡献就是
$$k\,w_c \pmod{23},$$
因为该类中的每个位置都带着同样的权重 \(w_c\)。因此我们不需要知道这一类内部具体放了哪些数字,只需要知道“该类数位和为 \(k\) 的方案数”。
对于大小为 \(u\) 的一类,其生成多项式为
$$G_u(x) = (1+x+x^2+\cdots+x^9)^u.$$
其中系数 \([x^k]G_u(x)\) 正好等于:用 \(u\) 个 \(0\) 到 \(9\) 的数字,使总和为 \(k\) 的方案数。
因为全局数位和只有 \(23\),所以次数高于 \(23\) 的项永远用不到。代码因此把每个多项式都截断到 \(x^{23}\),这样即使指数极大,也能通过二进制快速幂来处理。
每一类的大小不是 \(q\) 就是 \(q+1\)。因此只需要计算下面两个截断多项式:
$$G_q(x) = (1+x+\cdots+x^9)^q,$$
$$G_{q+1}(x) = (1+x+\cdots+x^9)^{q+1}.$$
它们都在模 \(10^9\) 下计算。函数 poly_pow_digit_sum 通过 repeated squaring 完成快速幂,乘法函数则直接丢弃所有高于 \(x^{23}\) 的项。
把每个类都变成“选择一个类和 \(k\),权重为 \([x^k]G_{u_c}(x)\)”之后,问题就变成一个只有 22 步的 DP。定义
$$DP[s][t]$$
表示处理完若干类之后,得到总数位和 \(s\)、且当前模 \(23\) 余数为 \(t\) 的方案数。
处理第 \(c\) 类时,若选择类和 \(k\),则转移为
$$s' = s+k,\qquad t' = (t + w_c k) \bmod 23.$$
该转移的方案数乘子就是 \([x^k]G_{u_c}(x)\)。全部 \(22\) 类处理完以后,答案就是
$$DP[23][0].$$
这正是为什么巨大的 \(n\) 变得无害:算法不再遍历所有位置,而只遍历 22 个剩余类。
程序用三个检查来验证自身:
$$S(9)=263626,$$
$$S(42)=6377168878570056,$$
以及检验压缩后的类 DP 与逐位直接 DP 在
$$n=200$$
时完全一致。直接 DP 只适合中等规模的 \(n\),但它是很强的验证工具,因为它证明了按模 \(22\) 分组并没有损失任何信息。
poly_mul_mod 负责相乘两个“数位和多项式”,并在次数 \(23\) 处截断。poly_pow_digit_sum 把 \(1+x+\cdots+x^9\) 提到 \(q\) 或 \(q+1\) 次幂。count_via_classes_mod 先构造 22 个权重 \(w_c\),再对 \((\text{数位和}, \text{余数})\) 做 DP,最终返回 \(DP[23][0]\)。direct_count_exact 和 direct_count_mod 只用于较小 \(n\) 的检查。
多项式乘法处理的是长度为 \(24\) 的向量,因此一次乘法代价为 \(O(23^2)\)。二进制快速幂的总代价为
$$O(23^2 \log n).$$
类 DP 要遍历 \(22\) 个类、\(24\) 个数位和状态、\(23\) 个余数、以及最多 \(24\) 个类和选择,所以复杂度为
$$O(22 \cdot 23^3),$$
对于固定目标和 \(23\) 来说几乎就是常数。空间复杂度为 \(O(23^2)\)。
Пусть \(S(n)\) — количество целых чисел
$$0 \le x < 10^n,$$
у которых сумма десятичных цифр равна \(23\), а само число делится на \(23\). Значит, в записи длины \(n\) разрешены ведущие нули; именно это и считает код. Цель Project Euler — найти \(S(11^{12}) \bmod 10^9\).
Запишем
$$x = \sum_{i=0}^{n-1} d_i 10^i,\qquad 0 \le d_i \le 9,$$
где \(d_0\) — цифра единиц. Тогда нужно одновременно иметь
$$\sum_{i=0}^{n-1} d_i = 23,$$
$$\sum_{i=0}^{n-1} d_i 10^i \equiv 0 \pmod{23}.$$
Прямой DP по всем \(n\) позициям невозможен при \(n = 11^{12}\), поэтому надо сжать позиции, которые одинаково ведут себя по модулю \(23\).
По модулю \(23\) степени числа \(10\) имеют период \(22\), потому что
$$10^{22} \equiv 1 \pmod{23}.$$
Следовательно, если \(i \equiv j \pmod{22}\), то
$$10^i \equiv 10^j \pmod{23}.$$
Все цифры на позициях из одного и того же класса по модулю \(22\) вносят вклад в критерий делимости с одним и тем же весом. Введём
$$w_c = 10^c \bmod 23,\qquad c=0,1,\ldots,21.$$
Если \(n = 22q + r\), где \(0 \le r < 22\), то класс \(c\) содержит
$$u_c = \begin{cases} q+1, & c < r, \\ q, & c \ge r. \end{cases}$$
Например, при \(n=25\) имеем \(q=1\), \(r=3\): классы \(0,1,2\) содержат по две позиции, остальные \(19\) классов — по одной.
Пусть сумма цифр внутри класса \(c\) равна \(k\). Тогда весь вклад этого класса по модулю \(23\) равен
$$k\,w_c \pmod{23},$$
потому что все позиции класса имеют одинаковый вес \(w_c\). Значит, точный рисунок цифр внутри класса не нужен; нужно лишь знать, сколько существует назначений с суммой класса \(k\).
Для класса размера \(u\) производящий полином равен
$$G_u(x) = (1+x+x^2+\cdots+x^9)^u.$$
Коэффициент \([x^k]G_u(x)\) как раз и есть число способов расставить \(u\) цифр от \(0\) до \(9\) так, чтобы их сумма была \(k\).
Поскольку общая сумма цифр всего \(23\), коэффициенты степеней выше \(23\) никогда не понадобятся. Поэтому код обрезает все полиномы после \(x^{23}\), и даже огромные показатели становятся управляемыми при бинарном возведении в степень.
Размер любого класса равен либо \(q\), либо \(q+1\). Значит, достаточно вычислить два обрезанных полинома
$$G_q(x) = (1+x+\cdots+x^9)^q,$$
$$G_{q+1}(x) = (1+x+\cdots+x^9)^{q+1},$$
оба по модулю \(10^9\). Функция poly_pow_digit_sum делает это быстрым возведением в степень, а умножение сразу отбрасывает все степени выше \(23\).
После сжатия каждого класса до вида «выбери сумму класса \(k\) с кратностью \([x^k]G_{u_c}(x)\)» остаётся 22-шаговый DP. Определим
$$DP[s][t]$$
как число способов, после обработки некоторых классов, получить общую сумму цифр \(s\) и остаток \(t \pmod{23}\).
При обработке класса \(c\) выбор суммы класса \(k\) даёт переход
$$s' = s+k,\qquad t' = (t + w_c k) \bmod 23.$$
Кратность этого перехода равна \([x^k]G_{u_c}(x)\). После обработки всех \(22\) классов ответом будет
$$DP[23][0].$$
Именно поэтому огромное значение \(n\) перестаёт быть проблемой: алгоритм не проходит по всем позициям, а только по \(22\) остаточным классам.
Реализация проверяет себя тремя тестами:
$$S(9)=263626,$$
$$S(42)=6377168878570056,$$
и сравнением, что сжатый DP по классам совпадает с прямым DP по позициям для
$$n=200.$$
Прямой DP возможен только для умеренных \(n\), но как проверка он очень силён: он подтверждает, что группировка по остаткам modulo \(22\) не теряет никакой информации.
poly_mul_mod перемножает два полинома по сумме цифр и обрезает результат по степени \(23\). poly_pow_digit_sum возводит \(1+x+\cdots+x^9\) в степень \(q\) или \(q+1\). count_via_classes_mod строит \(22\) веса \(w_c\), запускает DP по \((\text{сумма цифр}, \text{остаток})\) и возвращает \(DP[23][0]\). Функции direct_count_exact и direct_count_mod нужны только для проверок на меньших \(n\).
Полиномиальное умножение работает с векторами длины \(24\), так что одна операция стоит \(O(23^2)\). Быстрое возведение в степень therefore costs
$$O(23^2 \log n).$$
DP по классам проходит \(22\) класса, \(24\) значения суммы, \(23\) остатка и до \(24\) вариантов суммы класса, то есть требует
$$O(22 \cdot 23^3),$$
что практически константа при фиксированной целевой сумме \(23\). Память равна \(O(23^2)\).
لتكن \(S(n)\) عدد الأعداد الصحيحة
$$0 \le x < 10^n$$
التي يكون مجموع أرقامها العشرية \(23\) وتكون قابلة للقسمة على \(23\). وهذا يعني أن الأصفار البادئة مسموحة في التمثيل ذي الطول \(n\)، وهو بالضبط ما يحسبه الكود. هدف Project Euler هو إيجاد \(S(11^{12}) \bmod 10^9\).
نكتب
$$x = \sum_{i=0}^{n-1} d_i 10^i,\qquad 0 \le d_i \le 9,$$
حيث \(d_0\) هو رقم الآحاد. عندئذٍ يفرض السؤال شرطين معًا:
$$\sum_{i=0}^{n-1} d_i = 23,$$
$$\sum_{i=0}^{n-1} d_i 10^i \equiv 0 \pmod{23}.$$
إن بناء DP مباشر على جميع المواضع مستحيل عندما يكون \(n = 11^{12}\). لذلك لا بد من ضغط المواضع التي تتصرف بالطريقة نفسها modulo \(23\).
بترديد \(23\)، تتكرر قوى \(10\) بدورة طولها \(22\)، لأن
$$10^{22} \equiv 1 \pmod{23}.$$
ومن ثم إذا كان \(i \equiv j \pmod{22}\) فإن
$$10^i \equiv 10^j \pmod{23}.$$
إذن كل الأرقام الواقعة في الفئة نفسها modulo \(22\) لها الوزن نفسه في شرط القسمة. نعرّف
$$w_c = 10^c \bmod 23,\qquad c=0,1,\ldots,21.$$
وإذا كتبنا \(n = 22q + r\) مع \(0 \le r < 22\)، فإن عدد المواضع في الفئة \(c\) هو
$$u_c = \begin{cases} q+1, & c < r, \\ q, & c \ge r. \end{cases}$$
فمثلًا عند \(n=25\) يكون \(q=1\) و\(r=3\)، فتحتوي الفئات \(0,1,2\) على موضعين لكل منها، بينما تحتوي الفئات التسع عشرة الأخرى على موضع واحد لكل منها.
لنفترض أن مجموع الأرقام داخل الفئة \(c\) يساوي \(k\). عندئذ يكون إسهام هذه الفئة كلها modulo \(23\) هو ببساطة
$$k\,w_c \pmod{23},$$
لأن جميع المواضع في تلك الفئة تحمل الوزن نفسه \(w_c\). لذلك لا نحتاج إلى معرفة الترتيب الداخلي الكامل للأرقام داخل الفئة؛ يكفي أن نعرف عدد التعيينات التي تعطي مجموعًا فئويًا مقداره \(k\).
إذا كان حجم الفئة \(u\)، فإن كثير الحدود المولد هو
$$G_u(x) = (1+x+x^2+\cdots+x^9)^u.$$
ومعامل \([x^k]G_u(x)\) يساوي تمامًا عدد الطرق لاختيار \(u\) أرقام من \(0\) إلى \(9\) بحيث يكون مجموعها \(k\).
وبما أن مجموع الأرقام الكلي لا يتجاوز \(23\)، فلن نحتاج أبدًا إلى معاملات الدرجات الأعلى من \(23\). لهذا يقتطع الكود كل كثير حدود عند \(x^{23}\)، فتغدو الأسس الضخمة قابلة للمعالجة بالأس السريع الثنائي.
حجم كل فئة يساوي إما \(q\) أو \(q+1\). لذلك يكفي حساب كثيري الحدود المقتطعين
$$G_q(x) = (1+x+\cdots+x^9)^q,$$
$$G_{q+1}(x) = (1+x+\cdots+x^9)^{q+1},$$
وكلاهما modulo \(10^9\). تقوم الدالة poly_pow_digit_sum بحسابهما بالأس السريع، بينما تتخلص عملية الضرب مباشرة من كل الحدود الأعلى من \(x^{23}\).
بعد ضغط كل فئة إلى صيغة «اختر مجموعًا فئويًا \(k\) بمعامل \([x^k]G_{u_c}(x)\)» يتبقى لدينا DP من 22 خطوة فقط. نعرّف
$$DP[s][t]$$
على أنه عدد الطرق التي تعطي، بعد معالجة بعض الفئات، مجموع أرقام كليًا \(s\) وباقيًا \(t \pmod{23}\).
عند معالجة الفئة \(c\)، يؤدي اختيار مجموع فئوي \(k\) إلى الانتقال
$$s' = s+k,\qquad t' = (t + w_c k) \bmod 23.$$
ومضاعف هذا الانتقال هو \([x^k]G_{u_c}(x)\). وبعد معالجة الفئات الاثنتين والعشرين كلها تكون الإجابة المطلوبة هي
$$DP[23][0].$$
وهذا هو سبب زوال أثر ضخامة \(n\): فالخوارزمية لا تمر على جميع المواضع، بل على 22 فئة باقية فقط.
يتحقق التنفيذ من نفسه عبر ثلاث نقاط:
$$S(9)=263626,$$
$$S(42)=6377168878570056,$$
وكذلك مقارنة DP المضغوط حسب الفئات مع DP مباشر حسب المواضع عند
$$n=200.$$
الـ DP المباشر ممكن فقط عندما تكون \(n\) متوسطة الحجم، لكنه أداة تحقق ممتازة لأنه يثبت أن التجميع حسب البواقي modulo \(22\) لا يفقد أي معلومة.
تضرب الدالة poly_mul_mod كثيري حدود مجموع الأرقام وتقتطع الناتج عند الدرجة \(23\). وترفع poly_pow_digit_sum كثير الحدود \(1+x+\cdots+x^9\) إلى الأس \(q\) أو \(q+1\). ثم تبني count_via_classes_mod الأوزان \(w_c\) للفئات الاثنتين والعشرين، وتشغّل DP على الزوج \((\text{مجموع الأرقام}, \text{الباقي})\)، ثم تعيد \(DP[23][0]\). أما direct_count_exact وdirect_count_mod فمخصصتان فقط لنقاط التحقق عندما تكون \(n\) أصغر.
يُجرى ضرب كثيرات الحدود على متجهات طولها \(24\)، لذا تكلفة الضرب الواحد هي \(O(23^2)\). ومن ثم فإن الأس السريع يكلف
$$O(23^2 \log n).$$
أما DP الفئات فيمر عبر \(22\) فئة، و\(24\) حالة لمجموع الأرقام، و\(23\) باقيًا، وحتى \(24\) اختيارًا لمجموع الفئة، لذا تكلفته
$$O(22 \cdot 23^3),$$
وهي عمليًا ثابتة لأن مجموع الأرقام الهدف \(23\) ثابت. استهلاك الذاكرة هو \(O(23^2)\).