1) Déroulons le rendu glouton du montant
15.
Valeur
12 : le quotient
⌊15/12⌋ vaut
1, on sert une pièce de
12 et le reste devient
3.
Valeur
5 : le quotient
⌊3/5⌋ vaut
0, on ne sert rien et le reste vaut encore
3.
Valeur
1 : le quotient vaut
3, on sert trois pièces de
1 et le reste devient
0.
Le rendu glouton de
15 est donc
12+1+1+1, soit
quatre pièces.
Or
15=5+5+5 est un rendu du même montant, avec
trois pièces du système.
Comme
3<4, le rendu glouton de
15 n'est pas de cardinal minimal. Il existe donc un montant pour lequel le glouton n'est pas optimal : le système
(12,5,1) n'est pas canonique. La conclusion porte sur ce système et sur lui seul.
2) La fonction calcule d'abord le tableau des minima, puis recalcule le rendu glouton de chaque montant sans construire de liste.
def montants_en_defaut(pieces, borne):
"""pieces : valeurs entieres strictement positives, rangees par ordre decroissant.
borne : entier naturel.
Renvoie la liste croissante des montants de 1 a borne pour lesquels le rendu
glouton existe mais n'est pas de cardinal minimal. La liste recue n'est pas
modifiee."""
impossible = borne + 1
minimum = [0] + [impossible] * borne
for montant in range(1, borne + 1):
for valeur in pieces:
if valeur <= montant and minimum[montant - valeur] + 1 < minimum[montant]:
minimum[montant] = minimum[montant - valeur] + 1
defauts = []
for n in range(1, borne + 1):
reste = n
rendues = 0
for valeur in pieces:
rendues += reste // valeur
reste %= valeur
if reste == 0 and rendues > minimum[n]:
defauts.append(n)
return defauts
Justification de la récurrence. Notons
m(x) le nombre minimal de pièces totalisant
x, avec
m(x)=+∞ s'il n'existe aucun rendu de
x. On a
m(0)=0. Soit
x≥1 : tout rendu de
x contient au moins une pièce, dont la valeur
c vérifie
c≤x ; en la retirant, on obtient un rendu de
x−c, donc
m(x)≥1+m(x−c)≥1+min{m(x−c):c≤x}. Réciproquement, un rendu de
x−c augmenté d'une pièce de valeur
c est un rendu de
x, d'où l'inégalité inverse. Donc
m(x)=1+min{m(x−c) : c≤x}, le minimum étant pris sur les valeurs
c du système.
Comme les valeurs sont strictement positives, l'indice
x−c est strictement inférieur à
x : la boucle, qui remplit les cases par montants croissants, a déjà calculé cette case. L'invariant de la première boucle est donc : au début du tour d'indice
montant, les cases d'indices
0 à
montant - 1 contiennent le minimum cherché, ou la sentinelle s'il n'existe aucun rendu.
Choix de la sentinelle. Un rendu d'un montant
x≤ borne compte au plus
x pièces, puisque chaque pièce vaut au moins
1 ; la valeur
borne + 1 n'est donc atteinte par aucun rendu, et une case qui la contient encore à la fin du remplissage signale bien qu'aucun rendu n'existe pour ce montant. Un infini flottant,
float('inf'), conviendrait tout aussi bien :
inf + 1 vaut
inf, qui n'est pas strictement inférieur à
inf, si bien que la comparaison
minimum[montant - valeur] + 1 < minimum[montant] ne produit aucune mise à jour parasite. On préfère l'entier pour que la table ne contienne que des entiers, et parce que la sentinelle dit alors d'où elle vient.
Justification du second balayage. Pour un montant
n, la boucle sur les valeurs fait exactement ce que fait le rendu glouton, en comptant les pièces au lieu de les écrire : l'invariant est ici « la somme des valeurs déjà servies, ajoutée au reste, vaut
n », et la variable
rendues compte ces pièces au fur et à mesure. Le montant est retenu lorsque le glouton aboutit, c'est-à-dire
reste == 0, et qu'il sert strictement plus de pièces que le minimum.
3) Coût. Le remplissage du tableau fait
borne tours de
k tours, chacun de coût borné, soit
O(bk) en notant
b la borne. Le second balayage fait
borne tours de
k tours, également de coût borné, soit encore
O(bk). Le coût total est donc
O(bk). Appeler un tableau des minima distinct pour chaque montant, comme le ferait une recherche naïve du premier contre-exemple, coûterait
O(b2k) : le tableau unique, calculé une fois pour toutes, est ce qui rend le balayage abordable.
4) L'appel
print(montants_en_defaut([12, 5, 1], 60))
affiche
[15, 16, 20, 21, 27, 28, 32, 33, 39, 40, 44, 45, 51, 52, 56, 57]
Le plus petit montant en défaut est
15, celui de la question 1, et il y en a seize jusqu'à
60.
Pour les montants de
1 à
14 : aucun ne figure dans la liste, et le programme les a tous traités. Comme la question 2 a justifié que le tableau contient bien les minima et que le balayage reproduit bien le glouton, on a démontré que, pour chacun de ces quatorze montants, le rendu glouton est de cardinal minimal. C'est un résultat, acquis pour ces quatorze cas, et pour eux seuls.
Pour les montants supérieurs à
60 : le programme n'a rien calculé, donc il ne démontre rien. Il n'exclut ni d'autres défauts, ni une régularité. En revanche, chaque montant de la liste est un contre-exemple certain, que l'on peut dérouler à la main et recopier sur une copie.
À retenir : un balayage ne se lit que sur l'intervalle qu'il a parcouru ; il fournit des contre-exemples, jamais l'optimalité d'un glouton, qui se démontre par un argument d'échange.