diff --git a/Programmation_Dynamique/Corrige_Exercices.md b/Programmation_Dynamique/Corrige_Exercices.md deleted file mode 100644 index 8cc65ac..0000000 --- a/Programmation_Dynamique/Corrige_Exercices.md +++ /dev/null @@ -1,177 +0,0 @@ -# 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 - -Licence Creative Commons
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. diff --git a/Programmation_Dynamique/Corrige_TP_Donjon.md b/Programmation_Dynamique/Corrige_TP_Donjon.md deleted file mode 100644 index 523f400..0000000 --- a/Programmation_Dynamique/Corrige_TP_Donjon.md +++ /dev/null @@ -1,194 +0,0 @@ -# Corrigé — TP Le Donjon du Dragon - -## Partie 1 — Comprendre le problème - -**Question 1.** Chemins de `(0,0)` à `(2,3)` (grille 3×4) en allant uniquement droite/bas : -Il faut faire exactement 3 pas vers la droite et 2 pas vers le bas, soit C(5,2) = **10 chemins**. - -**Question 2.** Le chemin optimal est `(0,0)→(1,0)→(1,1)→(2,1)→(2,2)→(2,3)` : 3+5+9+2+8-5 = **22** d'or. - -| Chemin | Or total | -|--------|----------| -| Tout droite puis tout bas | 3-1+4+1+6-5 = **8** | -| Bas, droite×3, bas | 3+5+9-2+6-5 = **16** | -| Bas×2, droite×3 | 3+5-3+2+8-5 = **10** | -| Bas, droite, bas, droite×2 | 3+5+9+2+8-5 = **22** ← optimal | - -**Question 3.** Pour une grille `n×m`, le nombre de chemins est C(n+m-2, n-1) (combinaison : choisir quand descendre parmi tous les pas). - -## Partie 2 — Formulation récursive - -**Question 4.** Cas de base : -- `donjon(i, 0)` : on ne peut aller que vers le bas → somme de la colonne 0 jusqu'à `i` -- `donjon(0, j)` : on ne peut aller que vers la droite → somme de la ligne 0 jusqu'à `j` - -**Question 5.** Relation générale : -``` -donjon(i, j) = grille[i][j] + max(donjon(i-1, j), donjon(i, j-1)) -``` -*(On arrive en `(i,j)` depuis le haut ou depuis la gauche, on choisit le meilleur.)* - -**Question 6.** Version naïve : - -```python -def donjon_naif(grille, i, j): - if i == 0 and j == 0: - return grille[0][0] - if i == 0: - return grille[0][j] + donjon_naif(grille, 0, j - 1) - if j == 0: - return grille[i][0] + donjon_naif(grille, i - 1, 0) - return grille[i][j] + max(donjon_naif(grille, i - 1, j), - donjon_naif(grille, i, j - 1)) - -grille = [ - [ 3, -1, 4, 1], - [ 5, 9, -2, 6], - [-3, 2, 8, -5] -] -n, m = len(grille), len(grille[0]) -print(donjon_naif(grille, n - 1, m - 1)) # 22 -``` - -## Partie 3 — Version Top-Down (mémoïsation) - -**Question 7.** Oui, beaucoup de sous-problèmes sont recalculés. Par exemple, `donjon(1,1)` est appelé depuis `donjon(2,1)` et depuis `donjon(1,2)`. - -```python -compteur = 0 - -def donjon_naif_compte(grille, i, j): - global compteur - compteur += 1 - if i == 0 and j == 0: - return grille[0][0] - if i == 0: - return grille[0][j] + donjon_naif_compte(grille, 0, j - 1) - if j == 0: - return grille[i][0] + donjon_naif_compte(grille, i - 1, 0) - return grille[i][j] + max(donjon_naif_compte(grille, i - 1, j), - donjon_naif_compte(grille, i, j - 1)) - -donjon_naif_compte(grille, 2, 3) -print(f"Nombre d'appels : {compteur}") # bien plus que les 12 cases de la grille -``` - -**Question 8.** Version mémoïsation : - -```python -def donjon_memo(grille): - n = len(grille) - m = len(grille[0]) - memo = {} - - def donjon(i, j): - if (i, j) in memo: - return memo[(i, j)] - if i == 0 and j == 0: - resultat = grille[0][0] - elif i == 0: - resultat = grille[0][j] + donjon(0, j - 1) - elif j == 0: - resultat = grille[i][0] + donjon(i - 1, 0) - else: - resultat = grille[i][j] + max(donjon(i - 1, j), donjon(i, j - 1)) - memo[(i, j)] = resultat - return resultat - - return donjon(n - 1, m - 1) - -print(donjon_memo(grille)) # 22 -``` - -**Question 9.** Les deux versions donnent 22 ✓ - -## Partie 4 — Version Bottom-Up (tableau) - -**Question 10.** Tableau `t[i][j]` = or max depuis `(0,0)` jusqu'à `(i,j)` : - -``` -grille : tableau : -[ 3, -1, 4, 1] [ 3, 2, 6, 7] -[ 5, 9, -2, 6] [ 8, 17, 15, 21] -[-3, 2, 8, -5] [ 5, 19, 27, 22] -``` - -**Question 11.** Implémentation : - -```python -def donjon_bottom_up(grille): - n = len(grille) - m = len(grille[0]) - tableau = [[0] * m for _ in range(n)] - - tableau[0][0] = grille[0][0] - - for j in range(1, m): # première ligne - tableau[0][j] = tableau[0][j - 1] + grille[0][j] - - for i in range(1, n): # première colonne - tableau[i][0] = tableau[i - 1][0] + grille[i][0] - - for i in range(1, n): # reste du tableau - for j in range(1, m): - tableau[i][j] = grille[i][j] + max(tableau[i - 1][j], - tableau[i][j - 1]) - - return tableau[n - 1][m - 1] - -print(donjon_bottom_up(grille)) # 22 -``` - -**Question 12.** Les trois versions donnent 22 ✓ - -## Partie 5 — Bonus : retrouver le chemin optimal - -**Question 13.** Reconstruction du chemin : - -```python -def donjon_chemin(grille): - n = len(grille) - m = len(grille[0]) - tableau = [[0] * m for _ in range(n)] - - tableau[0][0] = grille[0][0] - for j in range(1, m): - tableau[0][j] = tableau[0][j - 1] + grille[0][j] - for i in range(1, n): - tableau[i][0] = tableau[i - 1][0] + grille[i][0] - for i in range(1, n): - for j in range(1, m): - tableau[i][j] = grille[i][j] + max(tableau[i - 1][j], - tableau[i][j - 1]) - - # Reconstruction : on remonte depuis (n-1, m-1) - chemin = [] - i, j = n - 1, m - 1 - while i > 0 or j > 0: - chemin.append((i, j)) - if i == 0: - j -= 1 - elif j == 0: - i -= 1 - elif tableau[i - 1][j] > tableau[i][j - 1]: - i -= 1 - else: - j -= 1 - chemin.append((0, 0)) - chemin.reverse() - - return tableau[n - 1][m - 1], chemin - - -valeur, chemin = donjon_chemin(grille) -print(f"Or maximal : {valeur}") # 22 -print(f"Chemin optimal : {chemin}") # [(0,0), (1,0), (1,1), (2,1), (2,2), (2,3)] -``` - ---- - -Auteur : Florian Mathieu - -Licence CC BY NC - -Licence Creative Commons
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. diff --git a/Programmation_Dynamique/Corrige_TP_Vaccin.md b/Programmation_Dynamique/Corrige_TP_Vaccin.md deleted file mode 100644 index e1e7b4f..0000000 --- a/Programmation_Dynamique/Corrige_TP_Vaccin.md +++ /dev/null @@ -1,190 +0,0 @@ -# Corrigé — TP L'Algorithme du Vaccin - -## Partie 1 — Comprendre le problème - -**Question 1.** Algorithme glouton (tri par ratio efficacité/temps décroissant) : - -Ratios : Gamma (15,0), Delta (14,3), Epsilon (14,2), Bêta (14,0), Alpha (13,3) - -- Prendre Gamma : 2 h utilisées, efficacité 30, reste 8 h -- Prendre Delta : 2+7=9 h utilisées, efficacité 130, reste 1 h -- Plus aucun anticorps ne tient en 1 h → arrêt - -**Résultat glouton : 130** (Gamma + Delta) - -**Question 2.** Oui : Alpha + Bêta + Gamma = 3+5+2 = 10 h, efficacité 40+70+30 = **140**. Le glouton a raté la solution optimale. - -**Question 3.** Pour 5 anticorps : 2⁵ = 32 combinaisons. Pour 30 anticorps : 2³⁰ ≈ 1 milliard. L'approche exhaustive devient impraticable très rapidement. - ---- - -## Partie 2 — Formulation récursive - -**Question 4.** Cas de base : -- `vaccin(0, t) = 0` (aucun anticorps disponible) -- `vaccin(i, 0) = 0` (temps épuisé) - -**Question 5.** Relation de récurrence : - -``` -vaccin(i, t) = vaccin(i-1, t) si duree_i > t -vaccin(i, t) = max(vaccin(i-1, t), sinon - eff_i + vaccin(i-1, t - duree_i)) -``` - -**Question 6.** Version naïve : - -```python -def vaccin_naif(anticorps, i, t): - if i == 0 or t == 0: - return 0 - duree, eff = anticorps[i - 1] - if duree > t: - return vaccin_naif(anticorps, i - 1, t) - else: - sans = vaccin_naif(anticorps, i - 1, t) - avec = eff + vaccin_naif(anticorps, i - 1, t - duree) - return max(sans, avec) - -anticorps = [(3, 40), (5, 70), (2, 30), (7, 100), (6, 85)] -n = len(anticorps) -print(vaccin_naif(anticorps, n, 10)) # 140 -``` - ---- - -## Partie 3 — Version Top-Down (mémoïsation) - -**Question 7.** Oui. Par exemple, `vaccin(2, 5)` (2 premiers anticorps, 5 h restantes) peut être appelé en ne prenant pas Alpha, mais aussi après avoir pris Alpha (si on avait commencé avec 8 h). Sans mémoïsation, ce sous-problème est recalculé à chaque fois. - -**Question 8.** Version mémoïsation : - -```python -def vaccin_memo(anticorps, temps_total): - memo = {} - - def vaccin(i, t): - if i == 0 or t == 0: - return 0 - if (i, t) in memo: - return memo[(i, t)] - duree, eff = anticorps[i - 1] - if duree > t: - resultat = vaccin(i - 1, t) - else: - sans = vaccin(i - 1, t) - avec = eff + vaccin(i - 1, t - duree) - resultat = max(sans, avec) - memo[(i, t)] = resultat - return resultat - - return vaccin(len(anticorps), temps_total) - -print(vaccin_memo(anticorps, 10)) # 140 -``` - -**Question 9.** Les deux versions donnent 140 ✓ - ---- - -## Partie 4 — Version Bottom-Up (tableau) - -**Question 10.** Tableau pour Alpha (3h,40), Bêta (5h,70), Gamma (2h,30), T=6 : - -| | t=0 | t=1 | t=2 | t=3 | t=4 | t=5 | t=6 | -|---|---|---|---|---|---|---|---| -| i=0 (aucun) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -| i=1 (Alpha 3h, 40) | 0 | 0 | 0 | 40 | 40 | 40 | 40 | -| i=2 (+Bêta 5h, 70) | 0 | 0 | 0 | 40 | 40 | 70 | 70 | -| i=3 (+Gamma 2h, 30) | 0 | 0 | 30 | 40 | 40 | 70 | 70 | - -Avec 6 h, le meilleur est Bêta seul (70), ou Gamma+Alpha ne tient pas car 2+3=5 h → efficacité 70 aussi. - -**Question 11.** Implémentation : - -```python -def vaccin_bottom_up(anticorps, temps_total): - n = len(anticorps) - tableau = [[0] * (temps_total + 1) for _ in range(n + 1)] - - for i in range(1, n + 1): - duree, eff = anticorps[i - 1] - for t in range(temps_total + 1): - if duree > t: - tableau[i][t] = tableau[i - 1][t] - else: - tableau[i][t] = max( - tableau[i - 1][t], - eff + tableau[i - 1][t - duree] - ) - - return tableau[n][temps_total] - -print(vaccin_bottom_up(anticorps, 10)) # 140 -``` - -**Question 12.** Les trois versions donnent 140 ✓ - ---- - -## Partie 5 — Reconstruction de la solution - -**Question 13.** Reconstruction : - -```python -def vaccin_reconstruction(anticorps, temps_total): - n = len(anticorps) - tableau = [[0] * (temps_total + 1) for _ in range(n + 1)] - - for i in range(1, n + 1): - duree, eff = anticorps[i - 1] - for t in range(temps_total + 1): - if duree > t: - tableau[i][t] = tableau[i - 1][t] - else: - tableau[i][t] = max( - tableau[i - 1][t], - eff + tableau[i - 1][t - duree] - ) - - # Reconstruction : on remonte le tableau - choisis = [] - t = temps_total - for i in range(n, 0, -1): - if tableau[i][t] != tableau[i - 1][t]: - choisis.append(anticorps[i - 1]) - t -= anticorps[i - 1][0] - - return tableau[n][temps_total], choisis - - -efficacite, choix = vaccin_reconstruction(anticorps, 10) -print(f"Efficacité maximale : {efficacite}") # 140 -print(f"Anticorps choisis : {choix}") # [(3,40), (5,70), (2,30)] → Alpha + Bêta + Gamma -``` - -**Question 14.** Vérification : -- Alpha (3h) + Bêta (5h) + Gamma (2h) = **10 h** ≤ 10 h ✓ -- 40 + 70 + 30 = **140** ✓ - -**Question 15.** Complexité : -- **Temps : O(n × T)** — on remplit un tableau de n+1 lignes et T+1 colonnes -- **Espace : O(n × T)** — pour stocker le tableau - -Avec n = nombre d'anticorps et T = temps total disponible. - ---- - -## Partie 6 — Bonus : contraintes supplémentaires - -**Question 16.** Pour gérer l'incompatibilité Alpha/Gamma, on peut ajouter une dimension à l'état : au lieu de `vaccin(i, t)`, on utilise `vaccin(i, t, alpha_pris)` où `alpha_pris` vaut 1 si Alpha a déjà été sélectionné, 0 sinon. Si `alpha_pris == 1` et que l'anticorps courant est Gamma (ou vice-versa), on interdit sa sélection. - -Cette approche généralise naturellement : chaque contrainte d'incompatibilité entre deux objets peut être encodée comme une variable booléenne supplémentaire dans l'état. La complexité devient O(n × T × 2^k) où k est le nombre de contraintes d'incompatibilité. - ---- - -Auteur : Florian Mathieu - -Licence CC BY NC - -Licence Creative Commons
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.