Das Problem kann in O (Polylog (b)) gelöst werden.
Wir definieren f(d, n)
die Anzahl der Ganzzahlen mit bis zu d Dezimalstellen, wobei die Ziffernsumme kleiner oder gleich n ist. Es ist ersichtlich, dass diese Funktion durch die Formel gegeben ist
Lassen Sie uns diese Funktion ableiten und mit etwas Einfacherem beginnen.
Die Funktion h zählt die Anzahl der Möglichkeiten, d - 1 Elemente aus einer Mehrfachmenge mit n + 1 verschiedenen Elementen auszuwählen. Dies ist auch die Anzahl der Möglichkeiten, n in d-Bins zu unterteilen. Dies lässt sich leicht erkennen, wenn man d-1-Zäune um n baut und jeden getrennten Abschnitt aufsummiert. Beispiel für n = 2, d = 3 ':
3-choose-2 fences number
-----------------------------------
11 ||11 002
12 |1|1 011
13 |11| 020
22 1||1 101
23 1|1| 110
33 11|| 200
Also zählt h alle Zahlen mit einer Ziffernsumme von n und d Ziffern. Außer es funktioniert nur für n weniger als 10, da die Ziffern auf 0 - 9 begrenzt sind. Um dies für die Werte 10 - 19 zu korrigieren, müssen wir die Anzahl der Partitionen mit einem Fach mit einer Nummer größer als 9 subtrahieren, die ich ab jetzt als übergelaufene Fächer bezeichnen werde.
Dieser Term kann berechnet werden, indem h wie folgt wiederverwendet wird. Wir zählen die Anzahl der Möglichkeiten, um n - 10 zu partitionieren, und wählen dann einen der Behälter aus, in den die 10 eingefügt werden soll, was dazu führt, dass die Anzahl der Partitionen einen übergelaufenen Behälter aufweist. Das Ergebnis ist die folgende vorläufige Funktion.
Wir fahren auf diese Weise für n kleiner oder gleich 29 fort, indem wir alle Arten der Partitionierung von n - 20 zählen und dann 2 Fächer auswählen, in die wir die Zehner einfügen, wodurch wir die Anzahl der Fächer mit 2 überfüllten Fächern zählen.
Aber an diesem Punkt müssen wir vorsichtig sein, da wir bereits die Partitionen gezählt haben, die in der vorherigen Amtszeit 2 übergelaufene Fächer hatten. Außerdem haben wir sie zweimal gezählt. Lassen Sie uns ein Beispiel verwenden und die Partition (10,0,11) mit der Summe 21 betrachten. Im vorherigen Term haben wir 10 subtrahiert, alle Partitionen der verbleibenden 11 berechnet und die 10 in eine der 3 Klassen gelegt. Diese Partition kann jedoch auf zwei Arten erreicht werden:
(10, 0, 1) => (10, 0, 11)
(0, 0, 11) => (10, 0, 11)
Da wir diese Partitionen auch einmal im ersten Term gezählt haben, beträgt die Gesamtanzahl der Partitionen mit 2 übergelaufenen Bins 1 - 2 = -1, sodass wir sie durch Hinzufügen des nächsten Terms erneut zählen müssen.
Wenn wir etwas genauer darüber nachdenken, stellen wir bald fest, dass die Häufigkeit, mit der eine Partition mit einer bestimmten Anzahl überlaufener Bins in einem bestimmten Begriff gezählt wird, durch die folgende Tabelle ausgedrückt werden kann (Spalte i steht für Begriff i, Zeile j, Partitionen mit j überlaufenen Bins) Behälter).
1 0 0 0 0 0 . .
1 1 0 0 0 0 . .
1 2 1 0 0 0 . .
1 4 6 4 1 0 . .
. . . . . .
. . . . . .
Ja, es ist Pascals Dreieck. Die einzige Anzahl, die uns interessiert, ist die in der ersten Zeile / Spalte, dh die Anzahl der Partitionen mit Null übergelaufenen Fächern. Und da die alternierende Summe jeder Zeile außer der ersten gleich 0 ist (z. B. 1 - 4 + 6 - 4 + 1 = 0), werden wir sie auf diese Weise los und gelangen zur vorletzten Formel.
Diese Funktion zählt alle Zahlen mit d Ziffern, die eine Ziffernsumme von n haben.
Was ist nun mit den Zahlen, deren Ziffernsumme kleiner als n ist? Wir können eine Standardwiederholung für Binomialzahlen plus ein induktives Argument verwenden, um dies zu zeigen
zählt die Anzahl der Partitionen mit höchstens n Ziffernsummen. Und daraus kann f mit den gleichen Argumenten wie für g abgeleitet werden.
Mit dieser Formel können wir zum Beispiel die Anzahl der schweren Zahlen im Intervall von 8000 bis 8999 ermitteln 1000 - f(3, 20)
, da sich in diesem Intervall tausend Zahlen befinden und wir die Anzahl der Zahlen mit einer Ziffernsumme von 28 oder weniger subtrahieren müssen Dabei wird berücksichtigt, dass die erste Ziffer bereits 8 zur Ziffernsumme beiträgt.
Als komplexeres Beispiel betrachten wir die Anzahl der schweren Zahlen im Intervall 1234..5678. Wir können zuerst in Schritten von 1 von 1234 bis 1240 gehen. Dann gehen wir in Schritten von 10 von 1240 bis 1300. Die obige Formel gibt uns die Anzahl der schweren Zahlen in jedem solchen Intervall:
1240..1249: 10 - f(1, 28 - (1+2+4))
1250..1259: 10 - f(1, 28 - (1+2+5))
1260..1269: 10 - f(1, 28 - (1+2+6))
1270..1279: 10 - f(1, 28 - (1+2+7))
1280..1289: 10 - f(1, 28 - (1+2+8))
1290..1299: 10 - f(1, 28 - (1+2+9))
Jetzt gehen wir von 1300 bis 2000 in Schritten von 100:
1300..1399: 100 - f(2, 28 - (1+3))
1400..1499: 100 - f(2, 28 - (1+4))
1500..1599: 100 - f(2, 28 - (1+5))
1600..1699: 100 - f(2, 28 - (1+6))
1700..1799: 100 - f(2, 28 - (1+7))
1800..1899: 100 - f(2, 28 - (1+8))
1900..1999: 100 - f(2, 28 - (1+9))
Von 2000 bis 5000 in Schritten von 1000:
2000..2999: 1000 - f(3, 28 - 2)
3000..3999: 1000 - f(3, 28 - 3)
4000..4999: 1000 - f(3, 28 - 4)
Jetzt müssen wir die Schrittweite wieder verringern, und zwar von 5000 auf 5600 in Schritten von 100, von 5600 auf 5670 in Schritten von 10 und schließlich von 5670 auf 5678 in Schritten von 1.
Ein Beispiel für eine Python-Implementierung (die inzwischen leicht optimiert und getestet wurde):
def binomial(n, k):
if k < 0 or k > n:
return 0
result = 1
for i in range(k):
result *= n - i
result //= i + 1
return result
binomial_lut = [
[1],
[1, -1],
[1, -2, 1],
[1, -3, 3, -1],
[1, -4, 6, -4, 1],
[1, -5, 10, -10, 5, -1],
[1, -6, 15, -20, 15, -6, 1],
[1, -7, 21, -35, 35, -21, 7, -1],
[1, -8, 28, -56, 70, -56, 28, -8, 1],
[1, -9, 36, -84, 126, -126, 84, -36, 9, -1]]
def f(d, n):
return sum(binomial_lut[d][i] * binomial(n + d - 10*i, d)
for i in range(d + 1))
def digits(i):
d = map(int, str(i))
d.reverse()
return d
def heavy(a, b):
b += 1
a_digits = digits(a)
b_digits = digits(b)
a_digits = a_digits + [0] * (len(b_digits) - len(a_digits))
max_digits = next(i for i in range(len(a_digits) - 1, -1, -1)
if a_digits[i] != b_digits[i])
a_digits = digits(a)
count = 0
digit = 0
while digit < max_digits:
while a_digits[digit] == 0:
digit += 1
inc = 10 ** digit
for i in range(10 - a_digits[digit]):
if a + inc > b:
break
count += inc - f(digit, 7 * len(a_digits) - sum(a_digits))
a += inc
a_digits = digits(a)
while a < b:
while digit and a_digits[digit] == b_digits[digit]:
digit -= 1
inc = 10 ** digit
for i in range(b_digits[digit] - a_digits[digit]):
count += inc - f(digit, 7 * len(a_digits) - sum(a_digits))
a += inc
a_digits = digits(a)
return count
Bearbeiten : Ersetzt den Code durch eine optimierte Version (die noch hässlicher aussieht als der Originalcode). Außerdem wurden ein paar Eckfälle behoben, während ich dabei war. heavy(1234, 100000000)
dauert ungefähr eine Millisekunde auf meiner Maschine.