We need the summatory function of squared divisor sums,
$$\Sigma_2(N)=\sum_{n=1}^{N}\sigma_2(n),\qquad \sigma_2(n)=\sum_{d\mid n} d^2,$$
for the very large input \(N=10^{15}\), and we only need the result modulo \(10^9\). A direct evaluation of \(\sigma_2(n)\) for every \(n\le N\) is far too slow, so the implementation reorganizes the summation and then skips large ranges at once.
Start from the definition of \(\sigma_2(n)\). If we sum over all \(n\le N\), every divisor \(d\) contributes its square \(d^2\) once for each multiple of \(d\) up to \(N\).
Therefore
$$\Sigma_2(N)=\sum_{n=1}^{N}\sum_{d\mid n} d^2=\sum_{d=1}^{N} d^2\left\lfloor\frac{N}{d}\right\rfloor.$$
This identity is the key simplification. Instead of asking for the divisors of each \(n\), we ask how many integers up to \(N\) are divisible by a fixed \(d\). That count is exactly \(\left\lfloor N/d\right\rfloor\).
The transformed sum still runs over \(d=1,2,\dots,N\), but the value
$$q(d)=\left\lfloor\frac{N}{d}\right\rfloor$$
does not change at every step. If the current block starts at \(l\), then the common quotient is
$$q=\left\lfloor\frac{N}{l}\right\rfloor,$$
and the largest index with the same quotient is
$$r=\left\lfloor\frac{N}{q}\right\rfloor.$$
So one whole block contributes
$$q\sum_{d=l}^{r} d^2.$$
After adding that block, the next unexplored position is \(r+1\). This is the reason the algorithm is fast: it jumps across entire intervals where the floor value is constant.
Let
$$S_2(x)=\sum_{k=1}^{x} k^2=\frac{x(x+1)(2x+1)}{6}.$$
Then the square sum over any interval is just a difference of prefixes:
$$\sum_{d=l}^{r} d^2=S_2(r)-S_2(l-1).$$
Substituting this into the block formula gives
$$\Sigma_2(N)=\sum_{[l,r]} \left\lfloor\frac{N}{l}\right\rfloor \bigl(S_2(r)-S_2(l-1)\bigr).$$
This is exactly the computation performed by the implementation.
There are two standard ways to see the bound. First, for \(d\le \sqrt N\) there can be at most \(\sqrt N\) starting positions. Second, once \(d>\sqrt N\), the quotient \(\left\lfloor N/d\right\rfloor\) is strictly less than \(\sqrt N\), so there are at most another \(\sqrt N\) distinct quotient values to process.
Hence the number of blocks is at most about \(2\sqrt N\), which turns a hopeless linear scan up to \(10^{15}\) into a practical computation.
The formula
$$S_2(x)=\frac{x(x+1)(2x+1)}{6}$$
is mathematically simple, but the product \(x(x+1)(2x+1)\) is enormous when \(x\) is close to \(10^{15}\). The C++, Python, and Java implementations therefore divide out the denominator before multiplying.
That cancellation is always valid. One of \(x\) and \(x+1\) is even, so a factor \(2\) can be removed there. Also, among \(x\), \(x+1\), and \(2x+1\), one is divisible by \(3\), so the remaining factor \(3\) can also be removed exactly. Only after these exact divisions do the implementations reduce modulo \(M\) and multiply the factors.
The C++ implementation additionally uses 128-bit intermediates for safe modular products. Python has arbitrary-precision integers, and the Java version follows the same cancellation pattern before modular multiplication.
Using the reordered identity directly,
$$\Sigma_2(6)=1^2\left\lfloor\frac{6}{1}\right\rfloor+2^2\left\lfloor\frac{6}{2}\right\rfloor+3^2\left\lfloor\frac{6}{3}\right\rfloor+4^2\left\lfloor\frac{6}{4}\right\rfloor+5^2\left\lfloor\frac{6}{5}\right\rfloor+6^2\left\lfloor\frac{6}{6}\right\rfloor.$$
So
$$\Sigma_2(6)=1\cdot 6+4\cdot 3+9\cdot 2+16\cdot 1+25\cdot 1+36\cdot 1=113.$$
The block method sees the same value in four groups:
$$[1,1]\to 6,\qquad [2,2]\to 12,\qquad [3,3]\to 18,\qquad [4,6]\to 77.$$
The total is again
$$6+12+18+77=113.$$
The implementation keeps an inclusive left boundary \(l\) for the current quotient block. It computes the common quotient \(q=\lfloor N/l\rfloor\), determines the matching right boundary \(r=\lfloor N/q\rfloor\), evaluates the range sum of squares modulo \(M\), multiplies by \(q\) modulo \(M\), adds the contribution to the running answer, and then moves on to \(l=r+1\).
No divisor enumeration is performed inside the block. The heavy work has already been absorbed into the closed form for \(S_2(x)\), so every iteration uses only a constant number of integer divisions and modular products.
The number of quotient blocks is \(O(\sqrt N)\), so the running time is \(O(\sqrt N)\). The memory usage is \(O(1)\), because the algorithm stores only the current block boundaries, the current quotient, and a few modular temporaries. Compared with naive divisor-based enumeration, this is the essential improvement that makes \(N=10^{15}\) feasible.
Gesucht ist die summierte Quadratsummen-Teilerfunktion
$$\Sigma_2(N)=\sum_{n=1}^{N}\sigma_2(n),\qquad \sigma_2(n)=\sum_{d\mid n} d^2,$$
für den sehr großen Wert \(N=10^{15}\). Ausgegeben wird nur das Ergebnis modulo \(10^9\). Eine direkte Berechnung aller Teilerquadratsummen bis \(N\) wäre viel zu langsam, daher formt die Lösung die Summe um und verarbeitet anschließend ganze Intervalle auf einmal.
Ausgehend von der Definition von \(\sigma_2(n)\) kann man zuerst über die Divisoren statt über die Zahlen \(n\) summieren. Jeder Divisor \(d\) trägt sein Quadrat \(d^2\) genau für diejenigen Vielfachen von \(d\) bei, die höchstens \(N\) sind.
Damit folgt
$$\Sigma_2(N)=\sum_{n=1}^{N}\sum_{d\mid n} d^2=\sum_{d=1}^{N} d^2\left\lfloor\frac{N}{d}\right\rfloor.$$
Die Bedeutung von \(\left\lfloor N/d\right\rfloor\) ist klar: Es ist die Anzahl der Vielfachen von \(d\) im Bereich \(1,\dots,N\).
Die transformierte Summe läuft zwar noch über alle \(d\), aber der Quotient
$$q(d)=\left\lfloor\frac{N}{d}\right\rfloor$$
ändert sich nicht in jedem Schritt. Beginnt ein Block bei \(l\), dann ist der gemeinsame Quotient
$$q=\left\lfloor\frac{N}{l}\right\rfloor,$$
und das größte \(r\) mit demselben Quotienten ist
$$r=\left\lfloor\frac{N}{q}\right\rfloor.$$
Der komplette Beitrag dieses Blocks ist daher
$$q\sum_{d=l}^{r} d^2.$$
Danach springt man direkt zu \(r+1\). Genau dieses Überspringen langer Intervalle macht das Verfahren effizient.
Mit der bekannten Formel für die Summe der Quadrate
$$S_2(x)=\sum_{k=1}^{x} k^2=\frac{x(x+1)(2x+1)}{6}$$
erhält man für ein Intervall
$$\sum_{d=l}^{r} d^2=S_2(r)-S_2(l-1).$$
Somit lässt sich die gesamte Aufgabe als Summe über Quotientenblöcke schreiben:
$$\Sigma_2(N)=\sum_{[l,r]} \left\lfloor\frac{N}{l}\right\rfloor \bigl(S_2(r)-S_2(l-1)\bigr).$$
Für \(d\le \sqrt N\) kann es höchstens \(\sqrt N\) verschiedene Startpositionen geben. Für \(d>\sqrt N\) gilt dagegen \(\left\lfloor N/d\right\rfloor<\sqrt N\), also bleiben höchstens \(\sqrt N\) verschiedene Quotientenwerte übrig.
Insgesamt entstehen damit nur ungefähr \(2\sqrt N\) Blöcke. Statt bis \(10^{15}\) linear zu iterieren, verarbeitet die Implementierung also nur eine Größenordnung von \(\sqrt N\) Zuständen.
Die Formel
$$S_2(x)=\frac{x(x+1)(2x+1)}{6}$$
ist zwar kompakt, aber das Produkt der drei Faktoren ist für große \(x\) riesig. Deshalb dividieren die C++-, Python- und Java-Implementierungen zunächst exakt durch \(2\) und \(3\), bevor modulo \(M\) multipliziert wird.
Diese Kürzung ist immer möglich. Von \(x\) und \(x+1\) ist genau einer gerade, also kann der Faktor \(2\) dort entfernt werden. Außerdem ist unter \(x\), \(x+1\) und \(2x+1\) immer ein Wert durch \(3\) teilbar, sodass auch der restliche Faktor \(3\) exakt verschwindet.
Erst danach werden die reduzierten Faktoren modulo \(M\) multipliziert. Die C++-Implementierung verwendet dafür zusätzlich 128-Bit-Zwischenwerte; Python arbeitet mit beliebig großen Ganzzahlen, und Java benutzt dieselbe Kürzungsstrategie vor der modularen Multiplikation.
Mit der vertauschten Summation ergibt sich
$$\Sigma_2(6)=1^2\left\lfloor\frac{6}{1}\right\rfloor+2^2\left\lfloor\frac{6}{2}\right\rfloor+3^2\left\lfloor\frac{6}{3}\right\rfloor+4^2\left\lfloor\frac{6}{4}\right\rfloor+5^2\left\lfloor\frac{6}{5}\right\rfloor+6^2\left\lfloor\frac{6}{6}\right\rfloor,$$
also
$$\Sigma_2(6)=1\cdot 6+4\cdot 3+9\cdot 2+16\cdot 1+25\cdot 1+36\cdot 1=113.$$
Blockweise erhält man dieselbe Summe über
$$[1,1]\to 6,\qquad [2,2]\to 12,\qquad [3,3]\to 18,\qquad [4,6]\to 77,$$
und somit
$$6+12+18+77=113.$$
Die Implementierung hält die linke Grenze \(l\) des aktuellen Quotientenblocks. Daraus werden der gemeinsame Quotient \(q=\lfloor N/l\rfloor\) und die rechte Grenze \(r=\lfloor N/q\rfloor\) bestimmt. Dann wird die Quadratsumme über \([l,r]\) modulo \(M\) berechnet, mit \(q\) multipliziert, zum Ergebnis addiert und direkt zum nächsten Block gesprungen.
Innerhalb eines Blocks werden keine einzelnen Teiler mehr untersucht. Die gesamte Arbeit steckt in der geschlossenen Formel für die Quadratsummen-Präfixe und in wenigen ganzzahligen Divisionen pro Iteration.
Da nur \(O(\sqrt N)\) Quotientenblöcke auftreten, beträgt die Laufzeit \(O(\sqrt N)\). Der Speicherbedarf ist \(O(1)\), weil nur die aktuellen Blockgrenzen, der aktuelle Quotient und einige modulare Zwischenwerte gehalten werden. Gegenüber einer naiven Teilerzählung ist das der entscheidende Unterschied für \(N=10^{15}\).
Aranan değer, kareli bölen toplamlarının birikimli halidir:
$$\Sigma_2(N)=\sum_{n=1}^{N}\sigma_2(n),\qquad \sigma_2(n)=\sum_{d\mid n} d^2.$$
Bu problemde \(N=10^{15}\) gibi çok büyük bir sayı için sonuç modulo \(10^9\) istenir. Her \(n\) için tüm bölenleri tek tek dolaşmak pratik değildir; bu yüzden çözüm toplamın sırasını değiştirir ve aynı bölüm değerlerini bloklar halinde işler.
\(\sigma_2(n)\) tanımına göre her bölen \(d\), \(n\)'nin böleni olduğu sürece \(d^2\) katkısı yapar. O halde tüm \(n\le N\) için toplarken, sabit bir \(d\) için kaç kez katkı yapıldığını saymak daha kolaydır.
Böylece
$$\Sigma_2(N)=\sum_{n=1}^{N}\sum_{d\mid n} d^2=\sum_{d=1}^{N} d^2\left\lfloor\frac{N}{d}\right\rfloor.$$
Buradaki \(\left\lfloor N/d\right\rfloor\), \(d\)'nin \(N\)'e kadar olan katlarının sayısıdır. Temel fikir budur: bölenleri aramak yerine kat sayısını saymak.
Dönüştürülmüş toplam hâlâ \(d=1\) ile \(N\) arasında gezer, fakat
$$q(d)=\left\lfloor\frac{N}{d}\right\rfloor$$
değeri her adımda değişmez. Blok sol sınırı \(l\) ise ortak bölüm
$$q=\left\lfloor\frac{N}{l}\right\rfloor$$
olur ve aynı bölümün geçerli olduğu en büyük sağ sınır
$$r=\left\lfloor\frac{N}{q}\right\rfloor$$
şeklindedir. Bu yüzden tüm blok katkısı
$$q\sum_{d=l}^{r} d^2$$
olur. Sonra doğrudan \(r+1\)'e atlanır. Hız kazancı tam olarak bu blok sıçramasından gelir.
Kareler toplamı için klasik kapalı form
$$S_2(x)=\sum_{k=1}^{x} k^2=\frac{x(x+1)(2x+1)}{6}$$
olduğundan, herhangi bir aralıktaki toplam
$$\sum_{d=l}^{r} d^2=S_2(r)-S_2(l-1)$$
olarak bulunur. Böylece tüm çözüm şu şekle iner:
$$\Sigma_2(N)=\sum_{[l,r]} \left\lfloor\frac{N}{l}\right\rfloor \bigl(S_2(r)-S_2(l-1)\bigr).$$
\(d\le \sqrt N\) için en fazla \(\sqrt N\) farklı başlangıç noktası olabilir. \(d>\sqrt N\) olduğunda ise \(\left\lfloor N/d\right\rfloor<\sqrt N\) olur; yani geriye en fazla \(\sqrt N\) farklı bölüm değeri kalır.
Dolayısıyla toplam blok sayısı yaklaşık \(2\sqrt N\) mertebesindedir. Bu gözlem, lineer taramayı uygulanabilir bir \(\sqrt N\) yöntemine dönüştürür.
Formül
$$S_2(x)=\frac{x(x+1)(2x+1)}{6}$$
çok basittir; ancak \(x\) büyükken çarpım devasa olur. Bu yüzden C++, Python ve Java uygulamaları önce paydadaki \(6\)'yı tam olarak sadeleştirir.
Bu her zaman mümkündür. \(x\) ve \(x+1\) sayılarından biri çifttir; dolayısıyla \(2\) oradan alınır. Ayrıca \(x\), \(x+1\) ve \(2x+1\) sayılarından biri mutlaka \(3\)'e bölünür; kalan \(3\) de oradan gider. Ancak bu tam bölmeler yapıldıktan sonra mod \(M\) altında çarpma yapılır.
C++ uygulaması güvenli çarpım için 128-bit ara değerler de kullanır. Python doğal olarak büyük tamsayılarla çalışır; Java uygulaması da aynı sadeleştirme düzenini izleyerek modüler çarpımı uygular.
Dönüştürülmüş özdeşliği doğrudan uygularsak
$$\Sigma_2(6)=1^2\left\lfloor\frac{6}{1}\right\rfloor+2^2\left\lfloor\frac{6}{2}\right\rfloor+3^2\left\lfloor\frac{6}{3}\right\rfloor+4^2\left\lfloor\frac{6}{4}\right\rfloor+5^2\left\lfloor\frac{6}{5}\right\rfloor+6^2\left\lfloor\frac{6}{6}\right\rfloor,$$
yani
$$\Sigma_2(6)=1\cdot 6+4\cdot 3+9\cdot 2+16\cdot 1+25\cdot 1+36\cdot 1=113.$$
Bloklara ayırınca da aynı sonucu görürüz:
$$[1,1]\to 6,\qquad [2,2]\to 12,\qquad [3,3]\to 18,\qquad [4,6]\to 77,$$
dolayısıyla
$$6+12+18+77=113.$$
Uygulama, mevcut bölüm bloğunun sol sınırı \(l\) ile başlar. Önce \(q=\lfloor N/l\rfloor\) bulunur, sonra aynı bölümün sürdüğü son konum \(r=\lfloor N/q\rfloor\) hesaplanır. Ardından \([l,r]\) aralığındaki kareler toplamı mod \(M\) altında bulunur, ortak bölümle çarpılır, cevaba eklenir ve bir sonraki blok için \(l=r+1\) yapılır.
Bu yapıda blok içinde tek tek bölen dolaşımı yoktur. Her iterasyonda sadece birkaç tamsayı bölme ve sabit sayıda modüler çarpım yapılır.
Blok sayısı \(O(\sqrt N)\) olduğu için toplam çalışma süresi \(O(\sqrt N)\) olur. Ek bellek kullanımı \(O(1)\)'dir; sadece güncel sınırlar, ortak bölüm ve birkaç ara modüler değer tutulur. \(N=10^{15}\) için işe yarayan nokta tam olarak budur.
Debemos calcular la suma acumulada de los cuadrados de los divisores,
$$\Sigma_2(N)=\sum_{n=1}^{N}\sigma_2(n),\qquad \sigma_2(n)=\sum_{d\mid n} d^2,$$
para el valor enorme \(N=10^{15}\), y solo se pide el resultado módulo \(10^9\). Calcular \(\sigma_2(n)\) por separado para cada \(n\) sería demasiado lento, así que la solución reordena la suma y luego agrupa los términos por bloques con el mismo cociente.
Partimos de la definición de \(\sigma_2(n)\). Si sumamos sobre todos los \(n\le N\), un divisor fijo \(d\) aporta \(d^2\) una vez por cada múltiplo de \(d\) que no excede \(N\).
Por tanto,
$$\Sigma_2(N)=\sum_{n=1}^{N}\sum_{d\mid n} d^2=\sum_{d=1}^{N} d^2\left\lfloor\frac{N}{d}\right\rfloor.$$
La cantidad \(\left\lfloor N/d\right\rfloor\) es simplemente el número de múltiplos de \(d\) en el intervalo \(1,\dots,N\). Esa observación elimina la necesidad de enumerar divisores para cada entero por separado.
Aunque la nueva suma aún recorre \(d=1,\dots,N\), el valor
$$q(d)=\left\lfloor\frac{N}{d}\right\rfloor$$
permanece constante en intervalos largos. Si un bloque empieza en \(l\), su cociente común es
$$q=\left\lfloor\frac{N}{l}\right\rfloor,$$
y el mayor extremo derecho con ese mismo cociente es
$$r=\left\lfloor\frac{N}{q}\right\rfloor.$$
Entonces todo el bloque aporta
$$q\sum_{d=l}^{r} d^2.$$
Después se salta directamente a \(r+1\). Esa es la aceleración esencial del método.
Sea
$$S_2(x)=\sum_{k=1}^{x} k^2=\frac{x(x+1)(2x+1)}{6}.$$
La suma de cuadrados en un intervalo cualquiera se obtiene por diferencia de prefijos:
$$\sum_{d=l}^{r} d^2=S_2(r)-S_2(l-1).$$
Así, la fórmula completa se convierte en
$$\Sigma_2(N)=\sum_{[l,r]} \left\lfloor\frac{N}{l}\right\rfloor \bigl(S_2(r)-S_2(l-1)\bigr).$$
Si \(d\le \sqrt N\), hay como mucho \(\sqrt N\) posiciones iniciales posibles. Si \(d>\sqrt N\), entonces \(\left\lfloor N/d\right\rfloor<\sqrt N\), así que solo quedan a lo sumo \(\sqrt N\) valores distintos del cociente.
En total aparecen unas \(2\sqrt N\) agrupaciones. Ese cambio convierte una iteración lineal imposible en un procedimiento del orden de \(\sqrt N\).
La identidad
$$S_2(x)=\frac{x(x+1)(2x+1)}{6}$$
es cerrada, pero el producto del numerador es gigantesco cuando \(x\) es grande. Por eso las implementaciones en C++, Python y Java cancelan primero el denominador \(6\) antes de multiplicar.
Esa cancelación siempre es exacta. Entre \(x\) y \(x+1\), uno es par, así que allí se elimina el factor \(2\). Además, entre \(x\), \(x+1\) y \(2x+1\), uno es múltiplo de \(3\), por lo que también se elimina el factor \(3\). Solo después de esas divisiones exactas se reducen los factores módulo \(M\) y se multiplican.
La implementación en C++ usa además enteros intermedios de 128 bits para multiplicar con seguridad. Python trabaja con enteros de precisión arbitraria, y Java sigue el mismo patrón algebraico antes de la multiplicación modular.
Aplicando directamente la suma reordenada,
$$\Sigma_2(6)=1^2\left\lfloor\frac{6}{1}\right\rfloor+2^2\left\lfloor\frac{6}{2}\right\rfloor+3^2\left\lfloor\frac{6}{3}\right\rfloor+4^2\left\lfloor\frac{6}{4}\right\rfloor+5^2\left\lfloor\frac{6}{5}\right\rfloor+6^2\left\lfloor\frac{6}{6}\right\rfloor,$$
de modo que
$$\Sigma_2(6)=1\cdot 6+4\cdot 3+9\cdot 2+16\cdot 1+25\cdot 1+36\cdot 1=113.$$
Por bloques, el mismo resultado aparece como
$$[1,1]\to 6,\qquad [2,2]\to 12,\qquad [3,3]\to 18,\qquad [4,6]\to 77,$$
y por tanto
$$6+12+18+77=113.$$
La implementación mantiene el extremo izquierdo \(l\) del bloque actual. A partir de él calcula el cociente común \(q=\lfloor N/l\rfloor\), obtiene el extremo derecho \(r=\lfloor N/q\rfloor\), evalúa la suma de cuadrados sobre \([l,r]\) módulo \(M\), multiplica por \(q\) módulo \(M\), añade esa contribución a la respuesta acumulada y avanza a \(l=r+1\).
No hay trabajo por divisor individual dentro del bloque. Toda la compresión proviene de usar la fórmula cerrada y de saltar directamente entre fronteras de cociente.
Como el número de bloques distintos es \(O(\sqrt N)\), el tiempo total es \(O(\sqrt N)\). El uso de memoria es \(O(1)\), ya que solo se guardan los límites del bloque actual, el cociente actual y unas pocas variables modulares. Esa reducción es la que hace posible tratar con \(N=10^{15}\).
目标是计算平方因子和的前缀和
$$\Sigma_2(N)=\sum_{n=1}^{N}\sigma_2(n),\qquad \sigma_2(n)=\sum_{d\mid n} d^2,$$
其中题目的规模是 \(N=10^{15}\),最后只需要结果对 \(10^9\) 取模。若对每个 \(n\) 单独枚举因子,再求 \(\sigma_2(n)\),规模上完全不可行,因此实现必须先改写求和,再按相同商值成块处理。
从定义出发,\(\sigma_2(n)\) 会把 \(n\) 的每个正因子 \(d\) 的平方 \(d^2\) 加进去一次。若对所有 \(n\le N\) 求和,那么固定某个 \(d\) 后,它会对所有不超过 \(N\) 的 \(d\) 的倍数各贡献一次。
因此可以得到
$$\Sigma_2(N)=\sum_{n=1}^{N}\sum_{d\mid n} d^2=\sum_{d=1}^{N} d^2\left\lfloor\frac{N}{d}\right\rfloor.$$
这里的 \(\left\lfloor N/d\right\rfloor\) 正是区间 \(1,\dots,N\) 中能被 \(d\) 整除的数的个数。这样就把“逐个找因子”的问题改写成了“统计倍数个数”的问题。
虽然新公式仍然对 \(d=1,\dots,N\) 求和,但
$$q(d)=\left\lfloor\frac{N}{d}\right\rfloor$$
并不会在每个 \(d\) 处都变化。若当前块的左端点为 \(l\),则这一整块的共同商值为
$$q=\left\lfloor\frac{N}{l}\right\rfloor,$$
而保持该商值不变的最大右端点是
$$r=\left\lfloor\frac{N}{q}\right\rfloor.$$
于是整个区间 \([l,r]\) 的贡献可以一次写成
$$q\sum_{d=l}^{r} d^2.$$
做完这一块之后,下一次直接跳到 \(r+1\)。算法的速度正是来自这种按块跳跃,而不是逐项推进。
平方和的经典公式是
$$S_2(x)=\sum_{k=1}^{x} k^2=\frac{x(x+1)(2x+1)}{6}.$$
因此任意区间上的平方和都能由两个前缀和之差得到:
$$\sum_{d=l}^{r} d^2=S_2(r)-S_2(l-1).$$
于是总公式变成
$$\Sigma_2(N)=\sum_{[l,r]} \left\lfloor\frac{N}{l}\right\rfloor \bigl(S_2(r)-S_2(l-1)\bigr).$$
这就是实现真正计算的内容。
当 \(d\le \sqrt N\) 时,最多只有 \(\sqrt N\) 个可能的起点。另一方面,当 \(d>\sqrt N\) 时,就有 \(\left\lfloor N/d\right\rfloor<\sqrt N\),因此剩余不同商值的个数也不超过 \(\sqrt N\)。
综合起来,块的总数大约只有 \(2\sqrt N\) 级别。这一点把原本不可能的线性遍历,压缩成了可行的平方根级算法。
公式
$$S_2(x)=\frac{x(x+1)(2x+1)}{6}$$
虽然简单,但当 \(x\) 接近 \(10^{15}\) 时,分子乘积会非常大。因此 C++、Python 和 Java 实现都先把分母 \(6\) 精确约掉,再做模乘。
这种约分总是成立。\(x\) 和 \(x+1\) 中一定有一个是偶数,所以因子 \(2\) 可以先消掉。对模 \(3\) 而言,\(x\)、\(x+1\)、\(2x+1\) 三个数中必有一个能被 \(3\) 整除,所以因子 \(3\) 也能精确消掉。完成这两个整除之后,再把各因子取模并相乘,就不会破坏正确性。
C++ 实现还使用 128 位中间结果来保证模乘安全。Python 使用任意精度整数,Java 则采用同样的代数约分顺序后再进行模运算。
直接代入换序后的公式,
$$\Sigma_2(6)=1^2\left\lfloor\frac{6}{1}\right\rfloor+2^2\left\lfloor\frac{6}{2}\right\rfloor+3^2\left\lfloor\frac{6}{3}\right\rfloor+4^2\left\lfloor\frac{6}{4}\right\rfloor+5^2\left\lfloor\frac{6}{5}\right\rfloor+6^2\left\lfloor\frac{6}{6}\right\rfloor,$$
所以
$$\Sigma_2(6)=1\cdot 6+4\cdot 3+9\cdot 2+16\cdot 1+25\cdot 1+36\cdot 1=113.$$
若按商值分块,则四个块分别是
$$[1,1]\to 6,\qquad [2,2]\to 12,\qquad [3,3]\to 18,\qquad [4,6]\to 77,$$
总和同样是
$$6+12+18+77=113.$$
实现维护当前块的左端点 \(l\)。先求出公共商值 \(q=\lfloor N/l\rfloor\),再由 \(r=\lfloor N/q\rfloor\) 找到这个商值对应的最右端点。随后计算区间 \([l,r]\) 的平方和对 \(M\) 取模后的结果,与 \(q\) 相乘并加入答案,最后把 \(l\) 更新为 \(r+1\)。
块内部不会再逐个处理因子。每次循环只需要常数次整数除法、两次前缀平方和计算以及若干次模乘。
由于不同商值块的数量是 \(O(\sqrt N)\),总时间复杂度为 \(O(\sqrt N)\)。额外空间复杂度为 \(O(1)\),因为算法只保存当前块边界、当前商值以及少量模运算临时变量。正是这种复杂度下降,才使 \(N=10^{15}\) 的情形可以直接计算。
Нужно вычислить суммарную функцию квадратов делителей
$$\Sigma_2(N)=\sum_{n=1}^{N}\sigma_2(n),\qquad \sigma_2(n)=\sum_{d\mid n} d^2,$$
для очень большого значения \(N=10^{15}\), а затем взять результат по модулю \(10^9\). Наивно считать \(\sigma_2(n)\) отдельно для каждого \(n\le N\) нельзя: это слишком медленно. Поэтому решение переставляет порядок суммирования и затем обрабатывает сразу большие блоки одинаковых частных.
По определению \(\sigma_2(n)\) каждый положительный делитель \(d\) числа \(n\) вносит вклад \(d^2\). Если суммировать по всем \(n\le N\), то удобнее зафиксировать \(d\) и посчитать, сколько раз он встретится как делитель.
Получаем тождество
$$\Sigma_2(N)=\sum_{n=1}^{N}\sum_{d\mid n} d^2=\sum_{d=1}^{N} d^2\left\lfloor\frac{N}{d}\right\rfloor.$$
Число \(\left\lfloor N/d\right\rfloor\) есть просто количество кратных \(d\), не превосходящих \(N\). Это и есть главный переход от наивной формулировки к эффективной.
В новой сумме индекс \(d\) пробегает весь диапазон, но значение
$$q(d)=\left\lfloor\frac{N}{d}\right\rfloor$$
остается постоянным на длинных отрезках. Если текущий блок начинается в точке \(l\), то его общее частное равно
$$q=\left\lfloor\frac{N}{l}\right\rfloor,$$
а максимальная правая граница с тем же значением равна
$$r=\left\lfloor\frac{N}{q}\right\rfloor.$$
Значит, вклад всего блока \([l,r]\) можно вычислить сразу:
$$q\sum_{d=l}^{r} d^2.$$
После этого алгоритм переходит сразу к \(r+1\), а не к \(l+1\). Именно этот прыжок по блокам и дает ускорение.
Обозначим
$$S_2(x)=\sum_{k=1}^{x} k^2=\frac{x(x+1)(2x+1)}{6}.$$
Тогда сумма квадратов на произвольном отрезке выражается через разность префиксов:
$$\sum_{d=l}^{r} d^2=S_2(r)-S_2(l-1).$$
Следовательно, вся задача сводится к сумме по блокам:
$$\Sigma_2(N)=\sum_{[l,r]} \left\lfloor\frac{N}{l}\right\rfloor \bigl(S_2(r)-S_2(l-1)\bigr).$$
Для \(d\le \sqrt N\) возможно не более \(\sqrt N\) стартовых позиций. Для \(d>\sqrt N\) уже выполняется \(\left\lfloor N/d\right\rfloor<\sqrt N\), то есть остается не более \(\sqrt N\) различных значений частного.
Итого получается примерно \(2\sqrt N\) блоков. Благодаря этому вместо линейного прохода до \(10^{15}\) возникает алгоритм порядка \(\sqrt N\).
Формула
$$S_2(x)=\frac{x(x+1)(2x+1)}{6}$$
удобна теоретически, но произведение чисел в числителе огромно. Поэтому реализации на C++, Python и Java сначала точно сокращают знаменатель \(6\), а уже потом выполняют умножение по модулю \(M\).
Такое сокращение всегда законно. Среди \(x\) и \(x+1\) одно число четное, значит фактор \(2\) убирается там. Кроме того, среди \(x\), \(x+1\) и \(2x+1\) одно число делится на \(3\), так что второй множитель знаменателя тоже сокращается без остатка.
После этих точных делений факторы берутся по модулю и перемножаются. В C++ для надежности используются 128-битные промежуточные значения; Python опирается на длинную арифметику, а Java использует ту же схему точного сокращения перед модульным умножением.
Из переставленной суммы сразу получаем
$$\Sigma_2(6)=1^2\left\lfloor\frac{6}{1}\right\rfloor+2^2\left\lfloor\frac{6}{2}\right\rfloor+3^2\left\lfloor\frac{6}{3}\right\rfloor+4^2\left\lfloor\frac{6}{4}\right\rfloor+5^2\left\lfloor\frac{6}{5}\right\rfloor+6^2\left\lfloor\frac{6}{6}\right\rfloor,$$
то есть
$$\Sigma_2(6)=1\cdot 6+4\cdot 3+9\cdot 2+16\cdot 1+25\cdot 1+36\cdot 1=113.$$
В блочном виде это те же четыре вклада:
$$[1,1]\to 6,\qquad [2,2]\to 12,\qquad [3,3]\to 18,\qquad [4,6]\to 77,$$
и снова
$$6+12+18+77=113.$$
Реализация хранит левую границу \(l\) текущего блока. Затем вычисляет общее частное \(q=\lfloor N/l\rfloor\), определяет правую границу \(r=\lfloor N/q\rfloor\), считает сумму квадратов на отрезке \([l,r]\) по модулю \(M\), умножает на \(q\), добавляет вклад к ответу и переходит к следующему блоку с \(l=r+1\).
Внутри блока не выполняется перебор делителей по одному. Вся компрессия достигается замкнутой формулой для префиксных сумм квадратов и переходом между границами равных частных.
Число блоков равно \(O(\sqrt N)\), значит время работы тоже \(O(\sqrt N)\). Дополнительная память равна \(O(1)\), потому что хранятся только текущие границы блока, текущее частное и несколько временных значений для модульной арифметики. Именно это делает возможным вычисление при \(N=10^{15}\).
نريد حساب المجموع التراكمي لمربعات القواسم:
$$\Sigma_2(N)=\sum_{n=1}^{N}\sigma_2(n),\qquad \sigma_2(n)=\sum_{d\mid n} d^2,$$
وذلك عند القيمة الكبيرة جدًا \(N=10^{15}\)، ثم نأخذ الناتج بترديد \(10^9\). الحساب المباشر لكل \(\sigma_2(n)\) على حدة غير عملي إطلاقًا، لذلك تعتمد الحلول على إعادة ترتيب المجموع ثم معالجة مجالات كاملة دفعة واحدة.
بحسب تعريف \(\sigma_2(n)\)، فإن كل قاسم موجب \(d\) للعدد \(n\) يضيف القيمة \(d^2\). وإذا جمعنا على كل \(n\le N\)، فمن الأسهل تثبيت \(d\) ثم عدّ عدد المرات التي يظهر فيها كقاسم.
فنحصل على الهوية
$$\Sigma_2(N)=\sum_{n=1}^{N}\sum_{d\mid n} d^2=\sum_{d=1}^{N} d^2\left\lfloor\frac{N}{d}\right\rfloor.$$
والكمية \(\left\lfloor N/d\right\rfloor\) هي ببساطة عدد مضاعفات \(d\) التي لا تتجاوز \(N\). بهذه الخطوة يتحول السؤال من فحص قواسم كل عدد إلى عدّ المضاعفات.
بعد إعادة ترتيب الجمع ما زلنا نجري المجموع على \(d=1,\dots,N\)، لكن القيمة
$$q(d)=\left\lfloor\frac{N}{d}\right\rfloor$$
لا تتغير عند كل خطوة. إذا بدأت الكتلة الحالية عند \(l\)، فإن خارج القسمة المشترك فيها هو
$$q=\left\lfloor\frac{N}{l}\right\rfloor,$$
وأكبر حد أيمن يبقي هذا الخارج ثابتًا هو
$$r=\left\lfloor\frac{N}{q}\right\rfloor.$$
إذن مساهمة الكتلة كلها مرة واحدة هي
$$q\sum_{d=l}^{r} d^2.$$
وبعد ذلك نقفز مباشرة إلى \(r+1\). هذا هو مصدر التسريع الحقيقي في الخوارزمية.
إذا عرّفنا
$$S_2(x)=\sum_{k=1}^{x} k^2=\frac{x(x+1)(2x+1)}{6},$$
فإن مجموع المربعات على أي مجال يصبح
$$\sum_{d=l}^{r} d^2=S_2(r)-S_2(l-1).$$
ومن ثم يمكن كتابة الحل كله على صورة مجموع فوق الكتل:
$$\Sigma_2(N)=\sum_{[l,r]} \left\lfloor\frac{N}{l}\right\rfloor \bigl(S_2(r)-S_2(l-1)\bigr).$$
عندما يكون \(d\le \sqrt N\)، لا يمكن أن يوجد أكثر من \(\sqrt N\) موضع بداية ممكن. وعندما يصبح \(d>\sqrt N\)، فإن \(\left\lfloor N/d\right\rfloor<\sqrt N\)، أي لا يبقى إلا عدد لا يتجاوز \(\sqrt N\) من قيم الخارج المختلفة.
إذن عدد الكتل الكلي من رتبة \(2\sqrt N\) تقريبًا. وهذا ما يحول المسح الخطي المستحيل إلى خوارزمية عملية من رتبة الجذر التربيعي.
الصيغة
$$S_2(x)=\frac{x(x+1)(2x+1)}{6}$$
بسيطة رياضيًا، لكن حاصل ضرب البسط ضخم جدًا عندما يكون \(x\) قريبًا من \(10^{15}\). لذلك تقوم تطبيقات C++ وPython وJava بإزالة العامل \(6\) بدقة قبل أي ضرب بترديد.
وهذا ممكن دائمًا. أحد العددين \(x\) و\(x+1\) زوجي، لذا يزال العامل \(2\) هناك. وكذلك من بين \(x\) و\(x+1\) و\(2x+1\) يوجد عدد واحد على الأقل يقبل القسمة على \(3\)، فيزال العامل \(3\) أيضًا دون أي تقريب.
بعد هذين الاختزالين فقط تُؤخذ العوامل بترديد \(M\) ثم تُضرب. تنفيذ C++ يستخدم قيمًا وسيطة بعرض 128 بت من أجل أمان الضرب، أما Python فيعتمد على الأعداد الصحيحة ذات الدقة غير المحدودة، وتنفذ Java النمط الجبري نفسه قبل الضرب بترديد.
من الهوية المعاد ترتيبها نحصل على
$$\Sigma_2(6)=1^2\left\lfloor\frac{6}{1}\right\rfloor+2^2\left\lfloor\frac{6}{2}\right\rfloor+3^2\left\lfloor\frac{6}{3}\right\rfloor+4^2\left\lfloor\frac{6}{4}\right\rfloor+5^2\left\lfloor\frac{6}{5}\right\rfloor+6^2\left\lfloor\frac{6}{6}\right\rfloor,$$
أي
$$\Sigma_2(6)=1\cdot 6+4\cdot 3+9\cdot 2+16\cdot 1+25\cdot 1+36\cdot 1=113.$$
وبصيغة الكتل تظهر المساهمة نفسها على أربعة مجالات:
$$[1,1]\to 6,\qquad [2,2]\to 12,\qquad [3,3]\to 18,\qquad [4,6]\to 77,$$
ومن ثم
$$6+12+18+77=113.$$
تحتفظ الشيفرة بالحد الأيسر \(l\) للكتلة الحالية. ثم تحسب الخارج المشترك \(q=\lfloor N/l\rfloor\)، وبعده تحدد الحد الأيمن \(r=\lfloor N/q\rfloor\). بعد ذلك تُحسب قيمة مجموع المربعات على المجال \([l,r]\) بترديد \(M\)، وتُضرب في \(q\)، ثم تُضاف إلى الجواب، وأخيرًا ينتقل التنفيذ إلى الكتلة التالية بجعل \(l=r+1\).
لا يوجد داخل الكتلة أي تعداد للقواسم واحدًا واحدًا. كل الضغط الحسابي جرى امتصاصه في الصيغة المغلقة لمجاميع المربعات وفي القفز بين حدود الكتل.
بما أن عدد الكتل المختلفة هو \(O(\sqrt N)\)، فإن زمن التنفيذ الكلي يساوي \(O(\sqrt N)\). أما الذاكرة الإضافية فهي \(O(1)\)، لأن الخوارزمية لا تخزن إلا حدود الكتلة الحالية والخارج الحالي وعددًا قليلًا من القيم الوسيطة في الحسابات المعيارية. هذا هو التحسين الذي يجعل التعامل مع \(N=10^{15}\) ممكنًا.