Le problème peut être résolu dans O (polylog (b)).
Nous définissons f(d, n)
comme le nombre d'entiers jusqu'à d chiffres décimaux avec une somme de chiffres inférieure ou égale à n. On voit que cette fonction est donnée par la formule
Dérivons cette fonction, en commençant par quelque chose de plus simple.
La fonction h compte le nombre de façons de choisir d - 1 éléments dans un multi-ensemble contenant n + 1 éléments différents. C'est aussi le nombre de façons de partitionner n en d cases, ce qui peut être facilement vu en construisant d - 1 clôtures autour de n unités et en résumant chaque section séparée. Exemple pour 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
Donc, h compte tous les nombres ayant une somme de n et d chiffres. Sauf que cela ne fonctionne que pour n inférieur à 10, car les chiffres sont limités à 0 - 9. Afin de résoudre ce problème pour les valeurs 10 à 19, nous devons soustraire le nombre de partitions ayant un bac avec un nombre supérieur à 9, que j'appellerai désormais des bacs survolés.
Ce terme peut être calculé en réutilisant h de la manière suivante. Nous comptons le nombre de façons de partitionner n - 10, puis choisissons l'un des casiers dans lequel placer les 10, ce qui entraîne le nombre de partitions ayant un casier débordé. Le résultat est la fonction préliminaire suivante.
Nous continuons de cette façon pour n inférieur ou égal à 29, en comptant toutes les façons de partitionner n - 20, puis en choisissant 2 bacs dans lesquels nous mettons les 10, comptant ainsi le nombre de partitions contenant 2 bacs débordés.
Mais à ce stade, nous devons être prudents, car nous avons déjà compté les partitions ayant 2 bacs survolés dans le terme précédent. Non seulement cela, mais en fait, nous les avons comptés deux fois. Prenons un exemple et examinons la partition (10,0,11) avec la somme 21. Dans le terme précédent, nous avons soustrait 10, calculé toutes les partitions des 11 restantes et placé les 10 dans l'une des 3 cases. Mais cette partition particulière peut être atteinte de deux manières:
(10, 0, 1) => (10, 0, 11)
(0, 0, 11) => (10, 0, 11)
Étant donné que nous avons également compté ces partitions une fois au premier trimestre, le nombre total de partitions avec 2 cases dépassées s'élève à 1 - 2 = -1, nous devons donc les compter à nouveau en ajoutant le terme suivant.
En y réfléchissant un peu plus, nous découvrons bientôt que le nombre de fois qu'une partition avec un nombre spécifique de bacs survolés est comptée dans un terme spécifique peut être exprimée par le tableau suivant (la colonne i représente le terme i, les rangées j partitions avec j survolé) bacs).
1 0 0 0 0 0 . .
1 1 0 0 0 0 . .
1 2 1 0 0 0 . .
1 4 6 4 1 0 . .
. . . . . .
. . . . . .
Oui, c'est le triangle de Pascals. Le seul décompte qui nous intéresse est celui de la première ligne / colonne, c'est-à-dire le nombre de partitions avec zéro bacs débordés. Et puisque la somme alternée de chaque ligne mais la première est égale à 0 (par exemple 1 - 4 + 6 - 4 + 1 = 0), c'est ainsi que nous nous en débarrassons et arrivons à l'avant-dernière formule.
Cette fonction compte tous les nombres avec d chiffres ayant une somme de n.
Maintenant, qu'en est-il des nombres dont la somme des chiffres est inférieure à n? Nous pouvons utiliser une récurrence standard pour les binômes plus un argument inductif, pour montrer que
compte le nombre de partitions avec somme numérique au plus n. Et de ce f peut être dérivé en utilisant les mêmes arguments que pour g.
En utilisant cette formule, nous pouvons par exemple trouver le nombre de nombres lourds dans l'intervalle de 8000 à 8999 car 1000 - f(3, 20)
, car il y a des milliers de nombres dans cet intervalle, et nous devons soustraire le nombre de nombres avec une somme de chiffres inférieure ou égale à 28 tout en tenant compte du fait que le premier chiffre contribue déjà 8 à la somme des chiffres.
Comme exemple plus complexe, regardons le nombre de nombres lourds dans l'intervalle 1234..5678. Nous pouvons d'abord passer de 1234 à 1240 par étapes de 1. Ensuite, nous passons de 1240 à 1300 par étapes de 10. La formule ci-dessus nous donne le nombre de nombres lourds dans chacun de ces intervalles:
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))
On passe maintenant de 1300 à 2000 par pas de 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))
De 2000 à 5000 par pas de 1000:
2000..2999: 1000 - f(3, 28 - 2)
3000..3999: 1000 - f(3, 28 - 3)
4000..4999: 1000 - f(3, 28 - 4)
Nous devons maintenant réduire à nouveau la taille des pas, en passant de 5000 à 5600 par pas de 100, de 5600 à 5670 par pas de 10 et enfin de 5670 à 5678 par pas de 1.
Un exemple d'implémentation Python (qui a reçu entre-temps de légères optimisations et tests):
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
Edit : Remplacé le code par une version optimisée (qui semble encore plus moche que le code d'origine). J'ai également corrigé quelques cas d'angle pendant que j'y étais. heavy(1234, 100000000)
prend environ une milliseconde sur ma machine.