# Corrigé — Exercices Programmation Dynamique
## Exercice 1 — L'escalier
**1.** Pour `n = 5` : escalier(5) = escalier(4) + escalier(3) = 5 + 3 = **8 façons**
**2.** La suite `escalier(n)` suit la **suite de Fibonacci** (décalée d'un rang) : 1, 2, 3, 5, 8, 13...
**3.** Relation de récurrence :
```
escalier(1) = 1
escalier(2) = 2
escalier(n) = escalier(n-1) + escalier(n-2) pour n > 2
```
**4.** Version mémoïsation :
```python
def escalier_memo(n):
memo = {1: 1, 2: 2}
def escalier(k):
if k in memo:
return memo[k]
memo[k] = escalier(k - 1) + escalier(k - 2)
return memo[k]
return escalier(n)
```
**5.** Version bottom-up :
```python
def escalier_bottom_up(n):
if n == 1:
return 1
tableau = [0] * (n + 1)
tableau[1] = 1
tableau[2] = 2
for i in range(3, n + 1):
tableau[i] = tableau[i - 1] + tableau[i - 2]
return tableau[n]
```
**6.** Vérification :
```python
for i in range(1, 11):
assert escalier_memo(i) == escalier_bottom_up(i), f"Erreur pour n={i}"
print("Toutes les valeurs correspondent !")
# Résultats : 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
```
## Exercice 2 — Rendu de monnaie revisité
**1.** Avec les pièces [2, 5, 10] et somme = 6 : le glouton prend 5 (la plus grande pièce ≤ 6), il reste 1 à rendre, mais aucune pièce de [2, 5, 10] ne permet de rendre 1. **Le glouton échoue.**
**2.** Oui. La programmation dynamique explore toutes les combinaisons et trouve : 2+2+2 = **3 pièces**. La somme 6 est bien rendable, le glouton était simplement mal parti en choisissant 5 en premier.
**3.** `rendu_bottom_up` :
```python
def rendu_bottom_up(pieces, somme):
tableau = [float('inf')] * (somme + 1)
tableau[0] = 0
for s in range(1, somme + 1):
for piece in pieces:
if piece <= s and tableau[s - piece] + 1 < tableau[s]:
tableau[s] = tableau[s - piece] + 1
return tableau[somme]
# Tests
print(rendu_bottom_up([2, 5, 10], 6)) # 3 (2+2+2)
print(rendu_bottom_up([1, 3, 4], 6)) # 2 (3+3)
print(rendu_bottom_up([1, 5, 6, 9], 11)) # 2 (5+6)
```
**4.** Version avec reconstruction :
```python
def rendu_avec_pieces(pieces, somme):
tableau = [float('inf')] * (somme + 1)
tableau[0] = 0
derniere_piece = [-1] * (somme + 1) # mémorise quelle pièce a été utilisée
for s in range(1, somme + 1):
for piece in pieces:
if piece <= s and tableau[s - piece] + 1 < tableau[s]:
tableau[s] = tableau[s - piece] + 1
derniere_piece[s] = piece
# Reconstruction
pieces_utilisees = []
s = somme
while s > 0:
p = derniere_piece[s]
pieces_utilisees.append(p)
s -= p
return tableau[somme], pieces_utilisees
print(rendu_avec_pieces([1, 3, 4], 6))
# (2, [3, 3])
```
## Exercice 3 — Sac à dos : à la main et en code
**1.** Tableau `tableau[i][w]` :
objets = [(1,2), (2,5), (3,8), (4,9)], capacité = 6
| | w=0 | w=1 | w=2 | w=3 | w=4 | w=5 | w=6 |
|---|-----|-----|-----|-----|-----|-----|-----|
| i=0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| i=1 (A:1kg,2) | 0 | 2 | 2 | 2 | 2 | 2 | 2 |
| i=2 (+B:2kg,5) | 0 | 2 | 5 | 7 | 7 | 7 | 7 |
| i=3 (+C:3kg,8) | 0 | 2 | 5 | 8 | 10 | 13 | 15 |
| i=4 (+D:4kg,9) | 0 | 2 | 5 | 8 | 10 | 13 | 15 |
**2.** Valeur maximale : **15**
**3.** Reconstruction depuis `tableau[4][6] = 15` :
- `tableau[4][6] = tableau[3][6]` → D non pris, w reste 6
- `tableau[3][6] = 15 ≠ tableau[2][6] = 7` → C pris (3kg), w = 6-3 = 3
- `tableau[2][3] = 7 ≠ tableau[1][3] = 2` → B pris (2kg), w = 3-2 = 1
- `tableau[1][1] = 2 ≠ tableau[0][1] = 0` → A pris (1kg), w = 0
Solution : **A + B + C** = 2+5+8 = 15 ✓
**4.** Vérification :
```python
objets = [(1, 2), (2, 5), (3, 8), (4, 9)]
valeur, choix = sac_reconstruction(objets, 6)
print(valeur, choix) # 15, [(3, 8), (2, 5), (1, 2)]
```
## Exercice 4 — Triangle de Pascal
**1.** Relation de récurrence :
```
pascal(0, 0) = 1
pascal(n, 0) = 1 ← bord gauche
pascal(n, n) = 1 ← bord droit
pascal(n, k) = pascal(n-1, k-1) + pascal(n-1, k) pour 0 < k < n
```
**2.** Implémentation bottom-up :
```python
def triangle_pascal(n):
triangle = []
for ligne in range(n):
t = [1] * (ligne + 1) # initialise avec des 1
for k in range(1, ligne): # cases intérieures
t[k] = triangle[ligne-1][k-1] + triangle[ligne-1][k]
triangle.append(t)
return triangle
```
**3.** Vérification :
```python
resultat = triangle_pascal(5)
assert resultat == [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]]
print("Correct !")
```
**4.** Bonus : La somme des éléments de la ligne `n` est `2ⁿ`. Par exemple, ligne 4 : 1+4+6+4+1 = 16 = 2⁴. Lien avec Fibonacci : la somme des diagonales "montantes" du triangle de Pascal donne les nombres de Fibonacci.
---
Auteur : Florian Mathieu
Licence CC BY NC
Ce cours est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International.