Das ist nicht wirklich Informatik ...
Sie erstellen eine Tabelle d, in der Sie die Summe der Teiler von k für k = 1 bis M speichern, wobei M = 5 ⋅106. Das ist der Teil, der zeitkritisch ist. Dann erstellen Sie eine Tabelle s, in der Sie die Summe der Teiler für alle 1 ≤ j ≤ k speichern, für k = 1 bis M. Das ist einfach,s0= 0, sk + 1=sk+dk + 1. Und dann ist f (L, R) =sR.- -sL - 1.
Die erste Tabelle ist das Problem. Sie erledigen das inO ( n logn ). Und du brauchst nur einen Faktor zwei, sagst du ...
Sie haben ein Array d mit 5 Millionen Einträgen, wahrscheinlich 4 Byte pro Eintrag = 20 Megabyte. Auf einem typischen Prozessor, den Sie in Ihrem Heimcomputer haben würden, passen 20 Megabyte nicht in einen Cache. Und Ihr Code führt viele Zugriffe auf Elemente dieses Arrays in quasi zufälliger Reihenfolge durch. Für jeden potentiellen Teiler k besuchen Sie alle Zahlen, die durch k teilbar sind, und erhöhen die Summe der Teiler um k.
Machen wir das mit weniger Besuchen: Wenn Sie j besuchen, das durch k teilbar ist, addieren Sie die beiden Teiler k und j / k. Aber wenn Sie das tun, beginnen Sie mitj =k2Fügen Sie nur k hinzu (weil k = j / k, und Sie möchten den Divisor nicht zweimal zählen), und fügen Sie dann k und j / k für weiteres j hinzu. Sie müssen nicht teilen, da j / k gleich k + 1, k + 2, k + 3 usw. ist. Wir initialisieren das Array für den Fall k = 1, dh setzen A [j] = 1 + j / 1 für j ≥ 2.
A [1] = 1
for (j = 2; j ≤ M; j += 1)
A [j] = 1 + j
for (k = 2; k*k ≤ M; k += 1)
j = k*k
A [j] += k
j += k
s = k + (k + 1)
while j ≤ M
A [j] += s
j += k
s += 1 // s equals k + j / k
Sie speichern keine Operationen. Sie greifen jetzt jedoch viel regelmäßiger auf das Array A zu, sodass Sie Zeit sparen, da der Zugriff auf die Elemente schneller erfolgt. j ist kleiner, wodurch die Anzahl der Iterationen für jedes j größer wird, wodurch die Verzweigungsvorhersage besser funktioniert.
Zur weiteren Verbesserung würden Sie herausfinden, wie viele Array-Elemente in den Prozessor-Cache Ihres Computers passen, und dann den gesamten Code nur für Unterbereiche des Arrays ausführen (z. B. nur A [0] in A [99999] ändern und dann A ändern [100000] bis A [199999] und so weiter). Auf diese Weise greifen die meisten Speicherzugriffe nur auf den Cache-Speicher zu, der möglicherweise wesentlich schneller ist.
Sie führen N Suchvorgänge in einer Tabelle der Größe M durch. Wenn M wesentlich größer als N ist, sollten Sie wahrscheinlich über Ansätze nachdenken, die diese Tabelle nicht erstellen und die pro Suche möglicherweise viel langsamer sind, aber insgesamt schneller aufgrund von die geringe Anzahl von Suchvorgängen. Selbst in dem Fall, in dem N ≤ 100.000 und M = 5.000.000 ist, können Sie beispielsweise die Teiler 1, 2, 3, 4, j / 1, j / 2, j / 3, j / 4 in der Tabelle nicht zählen (was ergibt es ist etwas schneller zu bauen) und handhaben das während der Suche.
Oder Sie können die Summe der Teiler nur für ungerade Zahlen addieren und dann die Summe der Teiler für gerade Zahlen berechnen (wenn die Summe der Teiler eines ungeraden k s ist, beträgt die Summe für 2k 3s, für 4k 7s für 8k sind es 15s usw.), was fast einen Faktor 2 einsparen würde.
PS. Ich habe es gemessen ... wodurch der Algorithmus zum Zählen aller Summen von Teilern cachefreundlicher wurde, indem sowohl j als auch k / j addiert wurden, was die Geschwindigkeit verdoppelte. Wenn Sie zuerst die Summe der Teiler für ungerade k berechnen und dann gerade k aus den ungeraden Werten berechnen, ist dies insgesamt siebenmal schneller. Offensichtlich alles nur konstante Faktoren.