Problem Summary

Let \(\tau(n)\) denote the number of positive divisors of \(n\). For fixed integers \(u\) and \(k\), define

$$M_n=\max_{n \le j \le n+k-1}\tau(j),\qquad S(u,k)=\sum_{n=1}^{u-k+1} M_n.$$

So each length-\(k\) interval contributes the largest divisor count appearing inside that interval, and the task is to add those maxima over all consecutive intervals from \(1\) up to \(u\). A naive method would recompute each maximum independently and would be far too slow for the full input size, so the solution splits the problem into two linear passes.

Mathematical Approach

Step 1: Formula for the Divisor Count

If

$$n=\prod_{r=1}^{t} p_r^{a_r},$$

then every divisor of \(n\) is obtained by choosing an exponent \(b_r\) with \(0 \le b_r \le a_r\) for each prime \(p_r\). Hence

$$\tau(n)=\prod_{r=1}^{t}(a_r+1).$$

This multiplicative formula is the starting point for the sieve. Instead of factoring each integer from scratch, the implementation builds all values \(\tau(1),\tau(2),\dots,\tau(u)\) incrementally.

Step 2: Linear Sieve Recurrence

Suppose \(n=p^a m\) with \(p \nmid m\). Then

$$\tau(n)=(a+1)\tau(m).$$

Multiplying by a prime gives two cases.

$$\tau(np)= \begin{cases} 2\tau(n), & p \nmid n,\\ \tau(n)\dfrac{a+2}{a+1}, & n=p^a m,\ p \nmid m. \end{cases}$$

The first case adds a new prime factor of exponent \(1\); the second increases the exponent of an existing prime factor from \(a\) to \(a+1\). The implementations maintain, for each integer, its smallest prime factor and the exponent of that smallest prime factor. That information is exactly what is needed to apply the recurrence in constant time when the next composite number is generated.

The sieve is linear because every composite number is produced once, in its canonical decomposition as a previous integer multiplied by a prime chosen under the smallest-prime-factor rule. As a result, the full divisor-count table up to \(u\) is built in \(O(u)\) time.

Step 3: Reformulate the Outer Sum as Sliding-Window Maxima

Once the table \(\tau(1),\dots,\tau(u)\) is available, the problem becomes a standard fixed-window maximum problem. While scanning integers from left to right, it is convenient to index windows by their right endpoint:

$$W_i=[\,i-k+1,\ i\,],\qquad i=k,k+1,\dots,u.$$

The contribution of window \(W_i\) is

$$\max_{j \in W_i}\tau(j).$$

This is the same set of windows as in the original definition, only written by ending position instead of starting position.

Step 4: Monotone Deque Invariant

The key data structure is a deque of candidate indices. It satisfies two invariants at all times:

1. indices in the deque are increasing from front to back;

2. divisor counts are strictly decreasing from front to back.

When a new index \(i\) arrives, all trailing indices \(q\) with \(\tau(q)\le \tau(i)\) are removed before \(i\) is appended. Then any front index \(q \le i-k\) is removed because it has left the current window.

After these two clean-up steps, the front of the deque is the index of the maximum divisor count in the current window, so its \(\tau\)-value is added to the answer.

Step 5: Why Removing Dominated Indices Is Correct

Assume \(q<i\) and \(\tau(q)\le \tau(i)\). From the moment \(i\) is inserted onward, any future window that still contains \(q\) also contains \(i\), because \(i\) is newer and therefore expires no earlier than \(q\). Since \(i\) has divisor count at least as large, \(q\) can never again be the unique best candidate for a maximum. So deleting \(q\) is always safe.

This domination argument is the whole reason the deque stays short and efficient: each index is inserted once, deleted at most once from the back, and deleted at most once from the front.

Worked Example

For \(u=10\) and \(k=3\), the divisor counts are

$$\tau(1),\tau(2),\dots,\tau(10)=1,2,2,3,2,4,2,4,3,4.$$

The window maxima are therefore

$$2,3,3,4,4,4,4,4,$$

so

$$S(10,3)=2+3+3+4+4+4+4+4=28.$$

The same mechanism scales to the full input because the expensive part is no longer the window search; it is just a single amortized-constant-time deque update per index.

How the Code Works

The C++, Python, and Java implementations all follow the same two-stage pipeline. First they build the divisor-count table up to \(u\) using the linear sieve recurrence above. Second they scan that table once with a monotone deque and accumulate the maximum value of each length-\(k\) window.

The optimized method is validated on a published checkpoint,

$$S(1000,10)=17176,$$

and one implementation also compares a smaller test case against a brute-force window scan to confirm that the optimized and naive methods agree.

Complexity Analysis

The divisor-count sieve runs in \(O(u)\) time. The deque scan is also \(O(u)\) because each index enters the deque once and leaves it at most once. Therefore the overall running time is

$$O(u).$$

The memory usage is \(O(u)\) for the divisor-count and sieve tables, plus \(O(k)\) for the deque. The asymptotic memory bound is therefore

$$O(u).$$

Further Reading

  1. Problem page: https://projecteuler.net/problem=485
  2. Divisor function: Wikipedia — Divisor function
  3. Linear sieve: cp-algorithms — Linear Sieve
  4. Monotone queue / sliding-window extrema: cp-algorithms — Minimum Queue / Maximum Queue

Problemzusammenfassung

Sei \(\tau(n)\) die Anzahl der positiven Teiler von \(n\). Für feste ganze Zahlen \(u\) und \(k\) definieren wir

$$M_n=\max_{n \le j \le n+k-1}\tau(j),\qquad S(u,k)=\sum_{n=1}^{u-k+1} M_n.$$

Jedes Fenster der Länge \(k\) liefert also genau die größte Teileranzahl innerhalb dieses Fensters, und alle diese Maxima werden addiert. Eine direkte Berechnung jedes Fensters wäre viel zu langsam, deshalb zerfällt die Lösung in zwei lineare Schritte: erst alle \(\tau\)-Werte bis \(u\) erzeugen, dann die Fenstermaxima effizient aufsummieren.

Mathematischer Ansatz

Schritt 1: Formel für die Teileranzahl

Hat \(n\) die Primfaktorzerlegung

$$n=\prod_{r=1}^{t} p_r^{a_r},$$

dann entsteht jeder Teiler durch die Wahl von Exponenten \(b_r\) mit \(0 \le b_r \le a_r\). Daher gilt

$$\tau(n)=\prod_{r=1}^{t}(a_r+1).$$

Diese multiplikative Struktur wird im Sieb ausgenutzt. Man faktorisiert also nicht jede Zahl separat, sondern baut die gesamte Tabelle \(\tau(1),\tau(2),\dots,\tau(u)\) sukzessive auf.

Schritt 2: Rekurrenz des linearen Siebs

Schreibe \(n=p^a m\) mit \(p \nmid m\). Dann ist

$$\tau(n)=(a+1)\tau(m).$$

Beim Multiplizieren mit einer Primzahl entstehen genau zwei Fälle:

$$\tau(np)= \begin{cases} 2\tau(n), & p \nmid n,\\ \tau(n)\dfrac{a+2}{a+1}, & n=p^a m,\ p \nmid m. \end{cases}$$

Im ersten Fall kommt ein neuer Primfaktor mit Exponent \(1\) hinzu, im zweiten Fall wird nur der vorhandene Exponent von \(a\) auf \(a+1\) erhöht. Die Implementierungen speichern deshalb zu jeder Zahl den kleinsten Primfaktor und den Exponenten dieses Primfaktors. Damit lässt sich der neue \(\tau\)-Wert sofort in \(O(1)\) berechnen.

Das Sieb ist linear, weil jede zusammengesetzte Zahl genau einmal erzeugt wird. Dadurch kostet die Konstruktion der gesamten Teileranzahl-Tabelle bis \(u\) nur \(O(u)\) Zeit.

Schritt 3: Umformulierung als gleitende Fenstermaxima

Ist die Tabelle \(\tau(1),\dots,\tau(u)\) bekannt, bleibt nur noch ein Standardproblem für feste Fensterlänge. Während man von links nach rechts läuft, beschreibt man das aktuelle Fenster bequem über seinen rechten Rand:

$$W_i=[\,i-k+1,\ i\,],\qquad i=k,k+1,\dots,u.$$

Der jeweilige Beitrag ist

$$\max_{j \in W_i}\tau(j).$$

Das sind genau dieselben Fenster wie in der ursprünglichen Definition von \(S(u,k)\), nur anders indiziert.

Schritt 4: Invariante der monotonen Deque

Die Lösung hält eine Deque mit Kandidatenindizes. Dabei gelten stets zwei Invarianten:

1. Die Indizes steigen von vorne nach hinten streng an.

2. Die zugehörigen \(\tau\)-Werte fallen von vorne nach hinten streng ab.

Kommt ein neuer Index \(i\), so werden zunächst alle hinteren Indizes \(q\) mit \(\tau(q)\le \tau(i)\) entfernt. Danach wird \(i\) angehängt. Anschließend werden vorne alle Indizes \(q \le i-k\) entfernt, weil sie nicht mehr im aktuellen Fenster liegen.

Danach steht am Kopf der Deque immer der Index mit der größten Teileranzahl im aktuellen Fenster.

Schritt 5: Warum dominierte Indizes gelöscht werden dürfen

Seien \(q<i\) und \(\tau(q)\le \tau(i)\). Ab dem Zeitpunkt, an dem \(i\) eingefügt wird, enthält jedes zukünftige Fenster, das \(q\) noch enthält, auch \(i\). Außerdem ist \(i\) mindestens so gut wie \(q\), weil sein \(\tau\)-Wert nicht kleiner ist und sein Index später aus dem Fenster fällt. Also kann \(q\) nie wieder das beste Maximum liefern und darf endgültig entfernt werden.

Genau deshalb ist die Gesamtkostenanalyse linear: Jeder Index wird höchstens einmal hinten entfernt und höchstens einmal vorne entfernt.

Durchgerechnetes Beispiel

Für \(u=10\) und \(k=3\) sind die Teileranzahlen

$$\tau(1),\tau(2),\dots,\tau(10)=1,2,2,3,2,4,2,4,3,4.$$

Die Fenstermaxima lauten

$$2,3,3,4,4,4,4,4,$$

also

$$S(10,3)=2+3+3+4+4+4+4+4=28.$$

Dasselbe Prinzip funktioniert für große Eingaben unverändert, weil pro Zahl nur eine konstante Zahl von Deque-Operationen anfällt.

Wie der Code arbeitet

Die C++-, Python- und Java-Implementierungen verwenden dieselbe Zweiphasen-Strategie. Zuerst wird die Tabelle der Teileranzahlen bis \(u\) mit dem linearen Sieb aufgebaut. Danach wird diese Tabelle genau einmal mit einer monotonen Deque durchlaufen, und das jeweilige Fenstermmaximum wird zur Summe addiert.

Als Kontrollwert wird

$$S(1000,10)=17176$$

verifiziert. Zusätzlich vergleicht eine Implementierung einen kleineren Testfall mit einer direkten Brute-Force-Berechnung, um die Optimierung abzusichern.

Komplexitätsanalyse

Das lineare Sieb benötigt \(O(u)\) Zeit. Der Deque-Durchlauf ist ebenfalls \(O(u)\), weil jeder Index nur einmal eingefügt und höchstens einmal entfernt wird. Insgesamt ergibt sich also

$$O(u)$$

Zeit. Für die Teileranzahl-Tabelle und die Hilfsinformationen des Siebs werden \(O(u)\) Speicherplätze benötigt; die Deque selbst braucht höchstens \(O(k)\). Asymptotisch dominiert damit

$$O(u).$$

Weiterführende Hinweise

  1. Problemseite: https://projecteuler.net/problem=485
  2. Teilerfunktion: Wikipedia — Teilerfunktion
  3. Lineares Sieb: cp-algorithms — Linear Sieve
  4. Monotone Queue / Sliding-Window-Maximum: cp-algorithms — Minimum Queue / Maximum Queue

Problem Özeti

\(\tau(n)\), \(n\) sayısının pozitif bölen sayısı olsun. Sabit \(u\) ve \(k\) için

$$M_n=\max_{n \le j \le n+k-1}\tau(j),\qquad S(u,k)=\sum_{n=1}^{u-k+1} M_n$$

tanımlanır. Yani uzunluğu \(k\) olan her ardışık aralık için içindeki en büyük bölen sayısını alır, sonra bu maksimumları toplarız. Her pencereyi bağımsız olarak taramak büyük girdi için çok yavaştır; bu yüzden çözüm iki doğrusal adıma ayrılır.

Matematiksel Yaklaşım

Adım 1: Bölen Sayısı Formülü

Eğer

$$n=\prod_{r=1}^{t} p_r^{a_r}$$

ise, \(n\)'nin her böleni her asal için \(0 \le b_r \le a_r\) olacak şekilde bir üs seçilerek elde edilir. Dolayısıyla

$$\tau(n)=\prod_{r=1}^{t}(a_r+1).$$

Lineer elekte kullanılan temel fikir budur. Her sayıyı ayrı ayrı çarpanlara ayırmak yerine \(\tau(1),\tau(2),\dots,\tau(u)\) tablosu artımlı biçimde üretilir.

Adım 2: Lineer Elek Reküransı

\(n=p^a m\) ve \(p \nmid m\) olsun. O zaman

$$\tau(n)=(a+1)\tau(m).$$

Bir asal ile çarpınca iki durum vardır:

$$\tau(np)= \begin{cases} 2\tau(n), & p \nmid n,\\ \tau(n)\dfrac{a+2}{a+1}, & n=p^a m,\ p \nmid m. \end{cases}$$

İlk durumda yeni bir asal çarpan eklenir ve üs \(1\) olur. İkinci durumda var olan aynı asalın üssü \(a\)'dan \(a+1\)'e çıkar. Bu nedenle uygulama her sayı için en küçük asal çarpanı ve bu asalın üstünü tutar; böylece yeni \(\tau\) değeri sabit zamanda güncellenir.

Her bileşik sayı tam bir kez üretildiği için bu yöntem gerçekten lineerdir ve tüm bölen sayısı tablosu \(O(u)\) zamanda elde edilir.

Adım 3: Dış Toplamı Kayan Pencere Maksimumuna Çevirme

\(\tau(1),\dots,\tau(u)\) hazır olduktan sonra geriye sabit uzunluklu pencere maksimumu problemi kalır. Sağ uçtan indekslemek uygundur:

$$W_i=[\,i-k+1,\ i\,],\qquad i=k,k+1,\dots,u.$$

Her pencerenin katkısı

$$\max_{j \in W_i}\tau(j)$$

olur. Bu, başlangıç indeksine göre yazılmış özgün tanımla tamamen aynı pencerelerdir.

Adım 4: Monotonik Deque Değişmezi

Çözüm aday indeksleri bir deque içinde tutar. Her an iki değişmez sağlanır:

1. İndeksler önden arkaya artar.

2. Karşılık gelen \(\tau\) değerleri önden arkaya sıkı biçimde azalır.

Yeni \(i\) geldiğinde, önce arkadaki ve \(\tau(q)\le \tau(i)\) koşulunu sağlayan tüm indeksler silinir. Sonra \(i\) eklenir. Ardından \(q \le i-k\) olan ön elemanlar çıkarılır; çünkü artık pencerenin dışındadırlar.

Bu işlemlerden sonra deque'nin başı mevcut pencerenin maksimumunu verir.

Adım 5: Baskılanan İndeksleri Silmek Neden Doğru?

\(q<i\) ve \(\tau(q)\le \tau(i)\) olsun. \(i\) eklendikten sonra \(q\)'yu içeren her gelecek pencere, daha yeni olduğu için \(i\)'yi de içerir. Ayrıca \(i\)'nin bölen sayısı daha küçük değildir ve pencereden daha geç çıkar. Bu yüzden \(q\), gelecekte hiçbir pencerede en iyi aday olamaz. Kalıcı olarak silinmesi güvenlidir.

Bu baskılama argümanı, her indeksin en fazla bir kez arkadan ve en fazla bir kez önden silinmesini garanti eder; dolayısıyla toplam deque maliyeti doğrusaldır.

Örnek

\(u=10\) ve \(k=3\) için bölen sayıları

$$\tau(1),\tau(2),\dots,\tau(10)=1,2,2,3,2,4,2,4,3,4.$$

Pencere maksimumları ise

$$2,3,3,4,4,4,4,4$$

olur. Dolayısıyla

$$S(10,3)=2+3+3+4+4+4+4+4=28.$$

Büyük girdi için de aynı yöntem çalışır; çünkü her adımda yalnızca amortize sabit sayıda deque işlemi yapılır.

Kodun Çalışma Mantığı

C++, Python ve Java uygulamalarının hepsi aynı iki aşamalı yapıyı kullanır. Önce lineer elek ile \(1\)'den \(u\)'ya kadar tüm bölen sayıları hesaplanır. Sonra bu tablo üzerinde tek geçişlik bir monotonik deque taraması yapılır ve her uzunluk-\(k\) pencerenin maksimumu toplama eklenir.

Doğrulama için

$$S(1000,10)=17176$$

kontrol edilir. Ayrıca bir uygulama, daha küçük bir örnekte sonucu kaba kuvvet yaklaşımıyla karşılaştırarak optimize yöntemi sınar.

Karmaşıklık Analizi

Lineer elek \(O(u)\) zamanda çalışır. Deque taraması da \(O(u)\)'dur; çünkü her indeks yalnızca bir kez eklenir ve en fazla bir kez silinir. Toplam çalışma süresi

$$O(u)$$

olur. Bellek kullanımı bölen sayısı ve elek tabloları için \(O(u)\), deque için en fazla \(O(k)\)'dir. Asimptotik olarak baskın terim

$$O(u)$$

bellektir.

İleri Okuma

  1. Problem sayfası: https://projecteuler.net/problem=485
  2. Bölen fonksiyonu: Wikipedia — Bölen fonksiyonu
  3. Lineer elek: cp-algorithms — Linear Sieve
  4. Monotonik kuyruk / kayan pencere maksimumu: cp-algorithms — Minimum Queue / Maximum Queue

Resumen del Problema

Sea \(\tau(n)\) el número de divisores positivos de \(n\). Para enteros fijos \(u\) y \(k\), definimos

$$M_n=\max_{n \le j \le n+k-1}\tau(j),\qquad S(u,k)=\sum_{n=1}^{u-k+1} M_n.$$

Cada bloque consecutivo de longitud \(k\) aporta el mayor valor de \(\tau\) que aparece dentro de ese bloque, y el problema pide sumar todos esos máximos. Recalcular el máximo en cada ventana de forma independiente sería demasiado costoso, así que la solución se divide en dos pasadas lineales.

Enfoque Matemático

Paso 1: Fórmula para el número de divisores

Si

$$n=\prod_{r=1}^{t} p_r^{a_r},$$

entonces cualquier divisor de \(n\) se obtiene eligiendo para cada primo un exponente \(b_r\) con \(0 \le b_r \le a_r\). Por lo tanto,

$$\tau(n)=\prod_{r=1}^{t}(a_r+1).$$

Esta identidad multiplicativa permite construir la tabla completa \(\tau(1),\tau(2),\dots,\tau(u)\) sin factorizar cada entero desde cero.

Paso 2: Recurrencia de la criba lineal

Escribamos \(n=p^a m\) con \(p \nmid m\). Entonces

$$\tau(n)=(a+1)\tau(m).$$

Al multiplicar por un primo aparecen exactamente dos casos:

$$\tau(np)= \begin{cases} 2\tau(n), & p \nmid n,\\ \tau(n)\dfrac{a+2}{a+1}, & n=p^a m,\ p \nmid m. \end{cases}$$

En el primer caso aparece un nuevo factor primo con exponente \(1\). En el segundo, solo aumenta en uno el exponente del primo ya presente. Las implementaciones guardan el menor factor primo y el exponente correspondiente para cada entero, de modo que la actualización de \(\tau\) sea inmediata.

La criba es lineal porque cada compuesto se genera una sola vez. Así, toda la tabla de números de divisores hasta \(u\) se obtiene en \(O(u)\).

Paso 3: Convertir la suma exterior en máximos de ventana deslizante

Una vez conocida la tabla \(\tau(1),\dots,\tau(u)\), el resto del trabajo consiste en máximos sobre ventanas de longitud fija. Es cómodo describir cada ventana por su extremo derecho:

$$W_i=[\,i-k+1,\ i\,],\qquad i=k,k+1,\dots,u.$$

La contribución de esa ventana es

$$\max_{j \in W_i}\tau(j).$$

Esto representa exactamente las mismas ventanas que la definición original de \(S(u,k)\).

Paso 4: Invariante de la deque monótona

Se mantiene una deque de índices candidatos con dos propiedades permanentes:

1. los índices aumentan de izquierda a derecha;

2. los valores de \(\tau\) disminuyen estrictamente de izquierda a derecha.

Cuando llega un nuevo índice \(i\), se eliminan por detrás todos los índices \(q\) con \(\tau(q)\le \tau(i)\). Después se inserta \(i\). Por último, se eliminan por delante los índices \(q \le i-k\), porque ya salieron de la ventana actual.

Tras estas operaciones, el frente de la deque contiene el máximo de la ventana actual.

Paso 5: Por qué es correcto descartar índices dominados

Supongamos que \(q<i\) y \(\tau(q)\le \tau(i)\). Desde que \(i\) entra en la deque, cualquier ventana futura que todavía contenga a \(q\) también contendrá a \(i\), ya que \(i\) es más reciente y expirará no antes que \(q\). Como además \(\tau(i)\) no es menor, \(q\) ya no puede volver a ser el mejor candidato. Por eso puede eliminarse para siempre.

Ese argumento garantiza que cada índice se inserta una sola vez y se elimina a lo sumo una vez por cada extremo de la deque, lo que mantiene el coste total lineal.

Ejemplo

Para \(u=10\) y \(k=3\), los valores son

$$\tau(1),\tau(2),\dots,\tau(10)=1,2,2,3,2,4,2,4,3,4.$$

Los máximos de las ventanas son

$$2,3,3,4,4,4,4,4,$$

y por tanto

$$S(10,3)=2+3+3+4+4+4+4+4=28.$$

La misma idea funciona para el caso grande porque en cada paso solo se hace un número amortizado constante de operaciones sobre la deque.

Cómo Funciona el Código

Las implementaciones en C++, Python y Java siguen exactamente las mismas dos etapas. Primero calculan todos los valores \(\tau(1),\dots,\tau(u)\) mediante la criba lineal. Después recorren esa tabla una sola vez con una deque monótona y van acumulando el máximo de cada ventana de longitud \(k\).

El método optimizado se comprueba con el valor

$$S(1000,10)=17176,$$

y una de las implementaciones también contrasta un caso más pequeño con una exploración por fuerza bruta.

Complejidad

La criba de divisores requiere \(O(u)\) tiempo. El recorrido con deque también cuesta \(O(u)\), porque cada índice entra una vez y sale como mucho una vez. En consecuencia, el tiempo total es

$$O(u).$$

La memoria usada es \(O(u)\) para las tablas de la criba y \(O(k)\) para la deque, así que el término dominante vuelve a ser

$$O(u).$$

Lecturas Adicionales

  1. Página del problema: https://projecteuler.net/problem=485
  2. Función divisor: Wikipedia — Función divisor
  3. Criba lineal: cp-algorithms — Linear Sieve
  4. Cola monótona / máximo en ventana deslizante: cp-algorithms — Minimum Queue / Maximum Queue

问题概述

记 \(\tau(n)\) 为 \(n\) 的正约数个数。对固定整数 \(u\) 与 \(k\),定义

$$M_n=\max_{n \le j \le n+k-1}\tau(j),\qquad S(u,k)=\sum_{n=1}^{u-k+1} M_n.$$

也就是说,对每个长度为 \(k\) 的连续区间,取其中最大的约数个数,再把所有这些最大值相加。若对每个区间都重新扫描一次,总代价会过高,因此实现把问题拆成两个线性阶段。

数学方法

步骤 1:约数个数公式

$$n=\prod_{r=1}^{t} p_r^{a_r},$$

那么 \(n\) 的任一约数都由每个质因子的指数选择 \(0 \le b_r \le a_r\) 决定,因此

$$\tau(n)=\prod_{r=1}^{t}(a_r+1).$$

这就是筛法的数学基础。与其对每个整数单独分解质因数,不如一次性构造 \(\tau(1),\tau(2),\dots,\tau(u)\) 的整张表。

步骤 2:线性筛递推

设 \(n=p^a m\),且 \(p \nmid m\)。则有

$$\tau(n)=(a+1)\tau(m).$$

当再乘上一个质数时,只有两种情况:

$$\tau(np)= \begin{cases} 2\tau(n), & p \nmid n,\\ \tau(n)\dfrac{a+2}{a+1}, & n=p^a m,\ p \nmid m. \end{cases}$$

第一种情况表示加入了一个新的质因子,其指数为 \(1\)。第二种情况表示原有同一质因子的指数从 \(a\) 增加到 \(a+1\)。实现会为每个整数保存其最小质因子以及该最小质因子的指数,于是新的 \(\tau\) 值可以在常数时间内更新。

由于每个合数只会按照唯一方式被生成一次,这个筛法的总时间是 \(O(u)\)。

步骤 3:把外层求和改写为滑动窗口最大值

在得到 \(\tau(1),\dots,\tau(u)\) 之后,剩下的问题就是固定长度窗口的最大值。用窗口右端点来编号最方便:

$$W_i=[\,i-k+1,\ i\,],\qquad i=k,k+1,\dots,u.$$

该窗口对答案的贡献是

$$\max_{j \in W_i}\tau(j).$$

这与原始定义中的所有窗口完全相同,只是换了一种索引方式。

步骤 4:单调双端队列的不变量

算法维护一个候选下标的双端队列,并始终保持两条性质:

1. 下标从队首到队尾严格递增;

2. 对应的 \(\tau\) 值从队首到队尾严格递减。

处理新下标 \(i\) 时,先删除队尾所有满足 \(\tau(q)\le \tau(i)\) 的下标 \(q\),再把 \(i\) 加入队尾。随后删除队首所有 \(q \le i-k\) 的下标,因为它们已经离开当前窗口。

完成这两步后,队首下标对应的就是当前窗口中的最大约数个数。

步骤 5:为什么可以删除被支配的下标

若 \(q<i\) 且 \(\tau(q)\le \tau(i)\),那么从 \(i\) 入队开始,任何仍然包含 \(q\) 的未来窗口也一定包含 \(i\),因为 \(i\) 更新、过期更晚。同时 \(i\) 的约数个数又不小于 \(q\)。因此 \(q\) 不可能再成为任何未来窗口中的最佳候选,可以永久删除。

这正是线性复杂度的原因:每个下标只会入队一次,至多从队尾弹出一次,再至多从队首弹出一次。

例子

取 \(u=10\)、\(k=3\)。这时

$$\tau(1),\tau(2),\dots,\tau(10)=1,2,2,3,2,4,2,4,3,4.$$

各个窗口的最大值为

$$2,3,3,4,4,4,4,4,$$

因此

$$S(10,3)=2+3+3+4+4+4+4+4=28.$$

在大规模输入中,算法仍然有效,因为每个位置只触发常数次摊还 deque 操作。

代码实现说明

C++、Python 和 Java 实现都遵循同样的两阶段结构。第一阶段用线性筛计算 \(1\) 到 \(u\) 的全部约数个数。第二阶段对这张表做一次单调队列扫描,把每个长度为 \(k\) 的窗口最大值累计到最终答案中。

实现会先验证检查点

$$S(1000,10)=17176,$$

其中一个实现还会把较小输入与朴素暴力扫描进行比较,以确认优化方法无误。

复杂度分析

线性筛耗时 \(O(u)\)。单调队列扫描同样是 \(O(u)\),因为每个下标最多进入和离开队列各一次。所以总时间复杂度为

$$O(u).$$

空间方面,筛表与约数个数表占用 \(O(u)\),双端队列最多占用 \(O(k)\),因此主导项仍然是

$$O(u).$$

延伸阅读

  1. 题目页面: https://projecteuler.net/problem=485
  2. 约数函数: Wikipedia — 約數函數
  3. 线性筛: cp-algorithms — Linear Sieve
  4. 单调队列与滑动窗口极值: cp-algorithms — Minimum Queue / Maximum Queue

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

Пусть \(\tau(n)\) обозначает число положительных делителей числа \(n\). Для фиксированных \(u\) и \(k\) вводятся величины

$$M_n=\max_{n \le j \le n+k-1}\tau(j),\qquad S(u,k)=\sum_{n=1}^{u-k+1} M_n.$$

То есть для каждого подряд идущего отрезка длины \(k\) нужно взять максимальное число делителей среди его элементов, а затем просуммировать такие максимумы по всем окнам. Если искать максимум в каждом окне отдельно, получится слишком медленно, поэтому решение состоит из двух линейных этапов.

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

Шаг 1: формула для числа делителей

Если

$$n=\prod_{r=1}^{t} p_r^{a_r},$$

то любой делитель числа \(n\) определяется выбором показателей \(b_r\), где \(0 \le b_r \le a_r\). Поэтому

$$\tau(n)=\prod_{r=1}^{t}(a_r+1).$$

Именно эта мультипликативная формула лежит в основе построения таблицы \(\tau(1),\tau(2),\dots,\tau(u)\) без отдельной факторизации каждого числа.

Шаг 2: рекуррентное обновление в линейном решете

Пусть \(n=p^a m\) и \(p \nmid m\). Тогда

$$\tau(n)=(a+1)\tau(m).$$

После умножения на простое число возникают два случая:

$$\tau(np)= \begin{cases} 2\tau(n), & p \nmid n,\\ \tau(n)\dfrac{a+2}{a+1}, & n=p^a m,\ p \nmid m. \end{cases}$$

В первом случае появляется новый простой множитель степени \(1\). Во втором случае степень уже существующего простого множителя увеличивается с \(a\) до \(a+1\). Поэтому реализации хранят для каждого числа его наименьший простой делитель и степень этого делителя; этого достаточно, чтобы обновлять \(\tau\) за \(O(1)\).

Каждое составное число порождается ровно один раз, поэтому построение всей таблицы до \(u\) работает за \(O(u)\).

Шаг 3: переход к максимумам в скользящем окне

Когда значения \(\tau(1),\dots,\tau(u)\) уже известны, остаётся задача о максимуме на окне фиксированной длины. Удобно индексировать окно его правой границей:

$$W_i=[\,i-k+1,\ i\,],\qquad i=k,k+1,\dots,u.$$

Вклад такого окна равен

$$\max_{j \in W_i}\tau(j).$$

Это те же самые окна, что и в исходном определении \(S(u,k)\), только записанные по-другому.

Шаг 4: инварианты монотонной деки

Используется дека индексов-кандидатов. Для неё постоянно поддерживаются два инварианта:

1. индексы возрастают от головы к хвосту;

2. соответствующие значения \(\tau\) строго убывают.

При обработке нового индекса \(i\) из хвоста удаляются все индексы \(q\) с \(\tau(q)\le \tau(i)\), затем \(i\) добавляется в хвост. После этого из головы удаляются все \(q \le i-k\), потому что они уже вышли из текущего окна.

После двух очисток в голове деки стоит индекс максимума для текущего окна.

Шаг 5: почему доминируемые индексы можно удалять навсегда

Пусть \(q<i\) и \(\tau(q)\le \tau(i)\). Начиная с момента вставки \(i\), любое будущее окно, которое ещё содержит \(q\), обязательно содержит и \(i\), потому что \(i\) новее и выпадет из окна не раньше. При этом значение \(\tau(i)\) не меньше. Значит, \(q\) уже никогда не станет лучшим кандидатом на максимум и может быть удалён окончательно.

Именно этот аргумент обеспечивает линейную сложность: каждый индекс добавляется один раз и удаляется не более одного раза с каждого конца деки.

Пример

Для \(u=10\) и \(k=3\) имеем

$$\tau(1),\tau(2),\dots,\tau(10)=1,2,2,3,2,4,2,4,3,4.$$

Максимумы по окнам равны

$$2,3,3,4,4,4,4,4,$$

поэтому

$$S(10,3)=2+3+3+4+4+4+4+4=28.$$

На больших входных данных работает тот же принцип: на каждый индекс приходится лишь константное амортизированное число операций с декой.

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

Реализации на C++, Python и Java используют одну и ту же схему. Сначала они строят таблицу числа делителей до \(u\) с помощью линейного решета. Затем эта таблица один раз просматривается монотонной декой, и максимум каждого окна длины \(k\) добавляется к ответу.

Оптимизированный метод проверяется по контрольному значению

$$S(1000,10)=17176,$$

а одна из реализаций дополнительно сравнивает меньший тест с прямым перебором окон.

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

Линейное решето требует \(O(u)\) времени. Проход с декой также работает за \(O(u)\), поскольку каждый индекс входит в деку один раз и выходит не более одного раза. Следовательно, общая временная сложность равна

$$O(u).$$

По памяти требуется \(O(u)\) для таблиц решета и значений \(\tau\), а сама дека занимает не более \(O(k)\). Асимптотически доминирует

$$O(u).$$

Дополнительные материалы

  1. Страница задачи: https://projecteuler.net/problem=485
  2. Функция числа делителей: Wikipedia — Функция числа делителей
  3. Линейное решето: cp-algorithms — Linear Sieve
  4. Монотонная дека и экстремумы на окне: cp-algorithms — Minimum Queue / Maximum Queue

ملخص المسألة

لتكن \(\tau(n)\) دالة عدد القواسم الموجبة للعدد \(n\). عند تثبيت العددين \(u\) و\(k\)، نعرّف

$$M_n=\max_{n \le j \le n+k-1}\tau(j),\qquad S(u,k)=\sum_{n=1}^{u-k+1} M_n.$$

أي إن كل نافذة متتالية طولها \(k\) تساهم بأكبر قيمة لعدد القواسم داخلها، ثم نجمع هذه القيم على جميع النوافذ. الفحص المباشر لكل نافذة على حدة سيكون بطيئًا جدًا، لذلك يعتمد الحل على مرحلتين خطيتين.

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

الخطوة 1: صيغة عدد القواسم

إذا كان

$$n=\prod_{r=1}^{t} p_r^{a_r},$$

فإن أي قاسم لـ \(n\) ينتج من اختيار أسّ \(b_r\) بحيث \(0 \le b_r \le a_r\) لكل عامل أولي. ومن ثم

$$\tau(n)=\prod_{r=1}^{t}(a_r+1).$$

هذه الصيغة الضربية هي أساس طريقة الغربال. بدلًا من تحليل كل عدد على حدة، تُبنى القيم \(\tau(1),\tau(2),\dots,\tau(u)\) كلها تدريجيًا.

الخطوة 2: علاقة التحديث في الغربال الخطي

اكتب \(n=p^a m\) حيث \(p \nmid m\). عندئذ

$$\tau(n)=(a+1)\tau(m).$$

وعند ضرب \(n\) في عدد أولي تظهر حالتان فقط:

$$\tau(np)= \begin{cases} 2\tau(n), & p \nmid n,\\ \tau(n)\dfrac{a+2}{a+1}, & n=p^a m,\ p \nmid m. \end{cases}$$

في الحالة الأولى نضيف عاملًا أوليًا جديدًا أسّه \(1\). وفي الحالة الثانية نزداد في أسّ العامل الأولي الموجود أصلًا من \(a\) إلى \(a+1\). لذلك تحفظ التطبيقات أصغر عامل أولي لكل عدد، وكذلك أسّه، حتى يمكن تحديث \(\tau\) بزمن ثابت.

يبقى الغربال خطيًا لأن كل عدد مركب يُولَّد مرة واحدة فقط وفق قاعدة أصغر عامل أولي، وبالتالي تُبنى الجدول كاملة حتى \(u\) في زمن \(O(u)\).

الخطوة 3: تحويل المجموع الخارجي إلى أعظميات نوافذ منزلقة

بعد حساب \(\tau(1),\dots,\tau(u)\)، تصبح المسألة مسألة أعظم قيمة في نافذة ثابتة الطول. من الملائم توصيف النافذة بحدّها الأيمن:

$$W_i=[\,i-k+1,\ i\,],\qquad i=k,k+1,\dots,u.$$

ومساهمة هذه النافذة هي

$$\max_{j \in W_i}\tau(j).$$

وهذه هي النوافذ نفسها الواردة في تعريف \(S(u,k)\)، لكن بصياغة مختلفة.

الخطوة 4: ثابتيات deque الأحادية

يُحافَظ على deque من الفهارس المرشحة مع خاصيتين دائمتيْن:

1. الفهارس مرتبة تصاعديًا من المقدمة إلى المؤخرة.

2. قيم \(\tau\) المقابلة لها مرتبة تنازليًا ترتيبًا صارمًا.

عند وصول الفهرس الجديد \(i\)، نحذف من الخلف كل فهرس \(q\) يحقق \(\tau(q)\le \tau(i)\)، ثم نضيف \(i\). وبعد ذلك نحذف من الأمام كل فهرس \(q \le i-k\) لأنه خرج من النافذة الحالية.

بعد هاتين العمليتين يكون عنصر المقدمة هو الفهرس الذي يعطي أكبر عدد قواسم داخل النافذة الحالية.

الخطوة 5: لماذا يجوز حذف الفهارس المهيمن عليها

إذا كان \(q<i\) و\(\tau(q)\le \tau(i)\)، فكل نافذة مستقبلية ما زالت تحتوي \(q\) ستحتوي أيضًا \(i\)، لأن \(i\) أحدث وسيغادر النافذة بعد \(q\) أو معه. وبما أن قيمة \(\tau(i)\) ليست أصغر، فلن يعود \(q\) أفضل مرشح لأي أعظمية مستقبلية. لذا يمكن حذفه نهائيًا بأمان.

هذا البرهان هو سبب الكلفة الخطية: كل فهرس يدخل deque مرة واحدة، ويخرج منها من الخلف مرة على الأكثر، ومن الأمام مرة على الأكثر.

مثال توضيحي

عند \(u=10\) و\(k=3\) نحصل على

$$\tau(1),\tau(2),\dots,\tau(10)=1,2,2,3,2,4,2,4,3,4.$$

وأعظميات النوافذ هي

$$2,3,3,4,4,4,4,4,$$

ومن ثم

$$S(10,3)=2+3+3+4+4+4+4+4=28.$$

ويعمل الأسلوب نفسه على المدخلات الكبيرة لأن كل خطوة تحتاج إلى عدد ثابت وسطيًا من عمليات deque.

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

تتبع تطبيقات C++ وPython وJava البنية نفسها. في المرحلة الأولى تُبنى قيم عدد القواسم حتى \(u\) باستعمال الغربال الخطي. وفي المرحلة الثانية تُمرَّر هذه القيم مرة واحدة عبر deque أحادية الرتابة، ويُضاف أكبر عنصر في كل نافذة طولها \(k\) إلى المجموع النهائي.

ويجري التحقق من صحة الطريقة بالقيمة

$$S(1000,10)=17176,$$

كما أن أحد التطبيقات يقارن حالة أصغر مع مسح مباشر بالقوة الغاشمة للتأكد من التطابق.

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

يعمل غربال عدد القواسم في زمن \(O(u)\). كما أن المرور بالـ deque يكلّف \(O(u)\) أيضًا لأن كل فهرس يُدرج مرة ويُزال مرة على الأكثر. لذلك يكون التعقيد الزمني الكلي

$$O(u).$$

أما الذاكرة فهي \(O(u)\) لجداول الغربال وقيم \(\tau\)، إضافة إلى \(O(k)\) للـ deque. وبالتالي يبقى الحد الغالب هو

$$O(u).$$

مراجع إضافية

  1. صفحة المسألة: https://projecteuler.net/problem=485
  2. دالة عدد القواسم: Wikipedia — دالة عدد القواسم
  3. الغربال الخطي: cp-algorithms — Linear Sieve
  4. deque أحادية الرتابة وأعظميات النوافذ: cp-algorithms — Minimum Queue / Maximum Queue