<?xml version="1.0" encoding="UTF-8"?>
<quiz>
<question type="category">
  <category>
    <text>$course$/QCM de NSI/Terminale/Programmation dynamique</text>
  </category>
  <info format="html">
    <text><![CDATA[<p>Sous-problèmes recouvrants, sous-structure optimale,<br/>
mémoïsation descendante, tabulation ascendante,<br/>
exemples classiques (suite de Fibonacci, plus longue<br/>
sous-séquence commune, sac à dos, distance d'édition,<br/>
rendu de monnaie).</p>]]></text>
  </info>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q01 : Idée centrale de la programmation dynamique</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Quel est le principe fondamental de la<br/>
programmation dynamique ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Le terme « programmation » dans « programmation<br/>
dynamique » vient de l'optimisation linéaire<br/>
(Bellman, années $1950$), et non de la<br/>
programmation informatique. Il signifie « plan,<br/>
tableau », ce qui justifie la notion de<br/>
tabulation.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Diviser le problème en deux moitiés et résoudre chacune indépendamment</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette description correspond au paradigme<br/>
« diviser pour régner ». La différence<br/>
essentielle est que les sous-problèmes y sont<br/>
<strong>disjoints</strong>, alors qu'en programmation<br/>
dynamique ils se <strong>recouvrent</strong>.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Faire à chaque étape le choix qui semble localement le meilleur</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette description correspond au principe des<br/>
algorithmes gloutons. La programmation<br/>
dynamique, au contraire, explore toutes les<br/>
possibilités de manière organisée, en<br/>
mémorisant les solutions des sous-problèmes.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Modifier le programme à mesure qu'il s'exécute</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette description évoque l'auto-modification<br/>
de code, qui n'a aucun rapport avec la<br/>
programmation dynamique. Le mot<br/>
« dynamique » désigne ici le caractère<br/>
temporel de la décision, pas une<br/>
modification du programme.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Mémoriser les solutions des sous-problèmes pour ne pas avoir à les recalculer</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>La programmation dynamique exploite la<br/>
présence de sous-problèmes qui se répètent<br/>
dans la décomposition récursive. En les<br/>
mémorisant, on évite la croissance<br/>
exponentielle du nombre de recalculs.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q02 : Mémoïsation</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Qu'est-ce que la <strong>mémoïsation</strong> ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Le mot « mémoïsation » associe les idées de<br/>
mémoire et de récursivité. C'est l'approche<br/>
« descendante » de la programmation dynamique :<br/>
on part du problème complet, on descend dans les<br/>
sous-problèmes en mémorisant les résultats au<br/>
passage.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La comparaison de deux algorithmes pour choisir le plus rapide</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette opération relève de la mesure de<br/>
performance, et n'a aucun rapport avec la<br/>
mémoïsation.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La conversion automatique d'une fonction récursive en boucle itérative</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette transformation s'appelle la<br/>
<strong>dérécursivation</strong>, et c'est une technique<br/>
différente. La mémoïsation conserve la<br/>
structure récursive de la fonction, en y<br/>
ajoutant simplement un cache.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La compression d'un programme en mémoire pour gagner de la place</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>La mémoïsation n'a aucun rapport avec la<br/>
taille du programme. Elle concerne le<br/>
<strong>temps de calcul</strong>, en évitant de<br/>
recalculer plusieurs fois la même valeur.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Le stockage des résultats déjà calculés dans un cache pour les réutiliser</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>À chaque appel récursif, l'algorithme<br/>
vérifie si le résultat est déjà connu<br/>
(typiquement dans un dictionnaire ou un<br/>
tableau) avant de le calculer. Si la valeur<br/>
est déjà disponible, elle est renvoyée<br/>
directement.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q03 : Tabulation</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Qu'appelle-t-on <strong>tabulation</strong> en programmation<br/>
dynamique ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>La mémoïsation et la tabulation sont les deux<br/>
faces de la programmation dynamique : la<br/>
mémoïsation est descendante (récursive, avec un<br/>
cache) ; la tabulation est ascendante<br/>
(itérative, avec un tableau). En pratique, la<br/>
tabulation est souvent plus rapide, car elle<br/>
évite le surcoût des appels récursifs.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Le découpage d'un problème en sous-problèmes égaux</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette description correspond au paradigme<br/>
« diviser pour régner ». La tabulation<br/>
consiste à remplir un tableau ; elle ne<br/>
découpe pas le problème.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La création d'une table HTML pour afficher les résultats</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>La « tabulation » dans ce contexte<br/>
algorithmique n'a aucun rapport avec une<br/>
mise en forme HTML. Il s'agit du<br/>
remplissage progressif d'un tableau de<br/>
valeurs.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Une optimisation des comparaisons utilisée dans un algorithme de tri</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>La tabulation est spécifique à la<br/>
programmation dynamique. Elle n'a pas de<br/>
rôle particulier dans les algorithmes de<br/>
tri.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>La construction d'un tableau de résultats, en partant des plus petits sous-problèmes pour aller vers les plus grands</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>C'est l'approche dite « ascendante » : on<br/>
part des cas de base et on remplit<br/>
progressivement un tableau, sans aucune<br/>
récursion.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q04 : Suite de Fibonacci en programmation dynamique</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Quelle est la complexité de la suite de Fibonacci<br/>
calculée avec mémoïsation ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Le passage de $O(2^n)$ à $O(n)$ pour la suite<br/>
de Fibonacci est l'exemple canonique de la<br/>
puissance de la programmation dynamique. Pour<br/>
$n = 50$, on passe ainsi de plus d'un million<br/>
d'appels à seulement $51$.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$O(n^2)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>La programmation dynamique appliquée à<br/>
Fibonacci ne nécessite qu'un parcours<br/>
linéaire des valeurs, et non une double<br/>
itération qui justifierait une complexité<br/>
quadratique.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$O(\log n)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Avec la récurrence $f(n) = f(n-1) + f(n-2)$,<br/>
on ne peut pas descendre en dessous de la<br/>
complexité linéaire avec une mémoïsation<br/>
standard. Une complexité logarithmique<br/>
existe, mais elle requiert des techniques<br/>
plus avancées (comme l'exponentiation<br/>
matricielle).</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$O(2^n)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette complexité correspond à la version<br/>
<strong>récursive naïve</strong>, sans mémoïsation. Le<br/>
recours à un cache réduit drastiquement ce<br/>
coût.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>$O(n)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Avec mémoïsation, chaque valeur<br/>
$\text{fib}(k)$ pour $k$ allant de $0$ à<br/>
$n$ n'est calculée qu'une seule fois, ce<br/>
qui donne $n$ calculs au total.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q05 : Sous-problèmes recouvrants</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Que signifie « le problème a des sous-problèmes<br/>
<strong>recouvrants</strong> » ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cette propriété est essentielle pour que la<br/>
programmation dynamique soit utile. Si les<br/>
sous-problèmes sont uniques, la mémoïsation<br/>
n'apporte rien et on retombe sur le paradigme<br/>
« diviser pour régner » classique.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Les sous-problèmes ont tous la même solution</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Les sous-problèmes peuvent avoir des<br/>
solutions très différentes, ce qui est<br/>
d'ailleurs le cas le plus courant. Le<br/>
caractère recouvrant signifie qu'ils<br/>
<strong>apparaissent plusieurs fois</strong> dans<br/>
l'arbre des appels récursifs, et non qu'ils<br/>
partagent leur solution.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Les sous-problèmes sont triés par taille décroissante</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>L'ordre dans lequel on considère les<br/>
sous-problèmes n'a rien à voir avec leur<br/>
caractère recouvrant. Le mot « recouvrant »<br/>
renvoie à la notion de <strong>doublon</strong>, pas<br/>
d'ordre.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Les sous-problèmes sont indépendants les uns des autres</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Si les sous-problèmes étaient indépendants,<br/>
on appliquerait plutôt le paradigme<br/>
« diviser pour régner ». La programmation<br/>
dynamique exploite précisément le contraire.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Plusieurs sous-problèmes peuvent apparaître plus d'une fois dans la décomposition récursive</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>C'est le cas typique de la suite de<br/>
Fibonacci, où la valeur $\text{fib}(k)$ est<br/>
calculée plusieurs fois dans la version<br/>
naïve. La programmation dynamique évite<br/>
précisément ces recalculs.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q06 : Sous-structure optimale</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Que signifie « le problème possède une<br/>
<strong>sous-structure optimale</strong> » ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>La sous-structure optimale et le caractère<br/>
recouvrant des sous-problèmes sont les deux<br/>
ingrédients qui rendent un problème adapté à la<br/>
programmation dynamique. Sans la première, on<br/>
ne peut pas combiner les solutions ; sans la<br/>
seconde, la mémoïsation ne sert à rien.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La taille des sous-problèmes diminue de manière logarithmique</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette description évoque la diminution<br/>
logarithmique du paradigme « diviser pour<br/>
régner » équilibré, mais elle ne correspond<br/>
pas à la définition de la sous-structure<br/>
optimale.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>La solution optimale du problème global est constituée des solutions optimales aux sous-problèmes</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette propriété permet de construire la<br/>
solution optimale en combinant les solutions<br/>
optimales des sous-problèmes. C'est elle qui<br/>
rend la programmation dynamique applicable.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Le problème peut être résolu en parallèle sur plusieurs processeurs</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>La sous-structure optimale est une propriété<br/>
mathématique du problème, et non une<br/>
caractéristique algorithmique. Le<br/>
parallélisme relève de l'implémentation.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Les sous-problèmes ont tous la même taille</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette uniformité de taille n'est pas<br/>
requise. La sous-structure optimale parle de<br/>
la composition des solutions, pas des<br/>
tailles des sous-problèmes.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q07 : Programmation dynamique et algorithme glouton</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Quelle est la différence principale entre un<br/>
<strong>algorithme glouton</strong> et un algorithme de<br/>
<strong>programmation dynamique</strong> ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Quand un problème possède la <strong>propriété de<br/>
choix glouton</strong> (un choix local mène à<br/>
l'optimum global), un algorithme glouton suffit<br/>
et reste plus efficace. Sinon, la programmation<br/>
dynamique est souvent nécessaire pour<br/>
atteindre l'optimum.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>L'algorithme glouton fait un choix local optimal sans revenir en arrière, tandis que la programmation dynamique explore toutes les possibilités via les sous-problèmes</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>L'algorithme glouton est plus rapide mais<br/>
peut manquer l'optimum global. La<br/>
programmation dynamique est plus coûteuse,<br/>
mais elle garantit l'optimum global lorsque<br/>
le problème possède la sous-structure<br/>
optimale.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>L'algorithme glouton est récursif, alors que la programmation dynamique est itérative</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Les deux approches peuvent être implémentées<br/>
aussi bien récursivement qu'itérativement.<br/>
C'est le <strong>principe</strong> qui les distingue, et<br/>
non leur forme syntaxique.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La programmation dynamique est utilisée uniquement pour les jeux vidéo</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>La programmation dynamique a un champ<br/>
d'application très large : alignement de<br/>
séquences, optimisation économique,<br/>
traitement automatique du langage, et bien<br/>
d'autres domaines.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>L'algorithme glouton ne fonctionne que sur les graphes</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Les algorithmes gloutons s'appliquent à de<br/>
très nombreux types de problèmes : rendu de<br/>
monnaie, planification d'activités, sac à<br/>
dos fractionnaire, et bien d'autres.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q08 : Structure de mémoïsation</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Quelle structure Python convient le mieux pour<br/>
mémoïser une fonction prenant un seul paramètre<br/>
entier positif ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Pour des paramètres entiers, la liste est<br/>
idéale. Pour des paramètres plus complexes<br/>
(chaînes, $p$-uplets, objets), le dictionnaire<br/>
devient nécessaire. Python propose aussi le<br/>
décorateur <code>@functools.lru_cache</code>, qui<br/>
automatise tout cela.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Une liste indexée par la valeur du paramètre</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Pour des paramètres entiers positifs dans<br/>
une plage connue, une liste donne un accès<br/>
en $O(1)$ très efficace. C'est la structure<br/>
la plus rapide.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Un fichier sur le disque</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Le disque est beaucoup trop lent pour de la<br/>
mémoïsation en mémoire. Il s'utiliserait<br/>
plutôt pour persister un cache entre deux<br/>
exécutions du programme.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Un dictionnaire avec les paramètres comme clés</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Un dictionnaire fonctionne, mais avec un<br/>
surcoût lié au calcul des empreintes. Pour<br/>
des indices entiers, la liste reste plus<br/>
rapide.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Une variable globale unique</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Une seule variable ne peut stocker qu'une<br/>
seule valeur, alors que la mémoïsation<br/>
demande d'en mémoriser plusieurs.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q09 : Origine du terme</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Le terme « programmation dynamique » a été<br/>
introduit en $1953$ par :</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Bellman a expliqué qu'il avait choisi le terme<br/>
« dynamique » pour évoquer le caractère<br/>
temporel de la décision, et « programmation »<br/>
au sens de planification. L'équation de Bellman<br/>
reste aujourd'hui un outil central en<br/>
optimisation et en apprentissage par<br/>
renforcement.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Tim Berners-Lee</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Tim Berners-Lee est l'inventeur du World<br/>
Wide Web. Son travail n'a aucun rapport<br/>
avec la programmation dynamique.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Alan Turing</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Alan Turing est principalement connu pour<br/>
la machine de Turing et le décryptage<br/>
d'Enigma, mais pas pour la programmation<br/>
dynamique.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Donald Knuth</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Donald Knuth est connu pour son traité<br/>
monumental « The Art of Computer<br/>
Programming », mais ce n'est pas lui qui<br/>
est à l'origine de la programmation<br/>
dynamique.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Richard Bellman</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Richard Bellman a inventé la programmation<br/>
dynamique pour traiter des problèmes<br/>
d'optimisation séquentielle. Le mot<br/>
« programmation » vient du jargon militaire<br/>
de l'époque, dans le sens de<br/>
« planification ».</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q10 : Exemple classique de programmation dynamique</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Lequel des problèmes suivants se résout<br/>
typiquement par programmation dynamique ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Autres exemples typiques : sac à dos, plus<br/>
longue sous-séquence commune, distance<br/>
d'édition, plus longue sous-suite croissante,<br/>
rendu de monnaie. Tous ont en commun la<br/>
sous-structure optimale et les sous-problèmes<br/>
recouvrants.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Tester si un nombre est premier</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Les tests de primalité ne se prêtent pas à<br/>
la programmation dynamique, car ils ne font<br/>
pas apparaître naturellement de<br/>
sous-problèmes recouvrants.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Inverser une chaîne de caractères</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>C'est une opération en $O(n)$ qui ne<br/>
requiert aucune mémoïsation. Aucune<br/>
décomposition en sous-problèmes ne<br/>
s'impose.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Trier un tableau</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Les algorithmes de tri usuels (tri par<br/>
insertion, par sélection, par fusion) ne<br/>
relèvent pas de la programmation<br/>
dynamique.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Calculer le plus court chemin dans un graphe pondéré</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>L'algorithme de Bellman-Ford repose sur la<br/>
programmation dynamique. Il exploite la<br/>
sous-structure optimale : le plus court<br/>
chemin de A à C contient un plus court<br/>
chemin de A à B, pour tout sommet B<br/>
intermédiaire.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q11 : Mémoïsation manuelle de Fibonacci</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>On écrit la fonction suivante :</p>
<pre><code>memo = {}
def fib(n):
    if n in memo:
        return memo[n]
    if n &lt;= 1:
        return n
    memo[n] = fib(n - 1) + fib(n - 2)
    return memo[n]</code></pre>
<p>Combien d'appels effectifs (qui calculent<br/>
réellement, sans utiliser le cache) sont<br/>
effectués pour <code>fib(10)</code> ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>La mémoïsation transforme la complexité<br/>
$O(2^n)$ en $O(n)$ : c'est un gain<br/>
algorithmique considérable. Le code reste<br/>
pourtant très proche de la version récursive<br/>
naïve.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Exactement un</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Il faut bien calculer chaque valeur<br/>
intermédiaire au moins une fois. La<br/>
mémoïsation évite les recalculs, pas les<br/>
calculs initiaux.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Environ onze</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Chaque valeur <code>fib(0)</code>, <code>fib(1)</code>, ...,<br/>
<code>fib(10)</code> est calculée une seule fois grâce<br/>
au cache. Les appels suivants sur une<br/>
valeur déjà calculée sont satisfaits<br/>
immédiatement par <code>memo</code>.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Plus de mille</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cet ordre de grandeur correspond à la<br/>
version sans mémoïsation. Avec un cache,<br/>
le nombre de calculs est bien plus<br/>
modeste.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Environ cent</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>C'est trop. Le nombre de valeurs distinctes<br/>
à calculer est seulement $n + 1$, soit<br/>
$11$, et non $n^2$.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q12 : Tabulation pour Fibonacci</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Quelle est la version <strong>tabulée</strong> (itérative) de<br/>
la suite de Fibonacci ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>L'approche tabulée évite la récursion et le<br/>
cache. Pour Fibonacci, on peut même optimiser<br/>
l'espace en ne gardant que les deux dernières<br/>
valeurs : <code>a, b = b, a + b</code>.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<pre><code>def fib(n):
    return round((1.618 ** n) / 2.236)</code></pre>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette formule est celle de Binet, qui<br/>
utilise le nombre d'or. Elle ne relève pas<br/>
de la programmation dynamique et devient<br/>
imprécise pour de grandes valeurs de $n$ à<br/>
cause des arrondis sur les flottants.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<pre><code>def fib(n):
    t = [0, 1]
    for i in range(2, n + 1):
        t.append(t[i-1] + t[i-2])
    return t[n]</code></pre>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>On remplit un tableau <code>t</code> de taille $n + 1$<br/>
en partant des cas de base ($t[0] = 0$ et<br/>
$t[1] = 1$), puis on construit les valeurs<br/>
suivantes par récurrence. La complexité<br/>
est $O(n)$ en temps comme en espace.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<pre><code>def fib(n):
    if n &lt;= 1: return n
    return fib(n-1) + fib(n-2)</code></pre>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Il s'agit de la version récursive <strong>naïve</strong>,<br/>
sans mémoïsation ni tabulation. Sa<br/>
complexité est $O(2^n)$.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<pre><code>def fib(n):
    return n * (n - 1) / 2</code></pre>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette formule donne la somme des entiers<br/>
de $0$ à $n - 1$, et non la suite de<br/>
Fibonacci.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q13 : Problème du sac à dos</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Le problème du <strong>sac à dos $0/1$</strong> consiste à<br/>
choisir un sous-ensemble d'objets à mettre dans<br/>
un sac de capacité fixée pour maximiser leur<br/>
valeur totale. Quelle est sa complexité en<br/>
programmation dynamique avec un sac de<br/>
capacité $W$ et $n$ objets ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>La complexité $O(nW)$ est dite<br/>
<strong>pseudo-polynomiale</strong> : elle est polynomiale<br/>
en la <strong>valeur</strong> de $W$, mais exponentielle en<br/>
sa <strong>taille en bits</strong>. Le sac à dos reste donc<br/>
un problème difficile en taille d'entrée<br/>
stricte.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$O(n!)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Aucune raison particulière n'amène à une<br/>
complexité factorielle pour ce problème.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$O(n)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Il faut considérer chaque objet pour chaque<br/>
capacité possible du sac. La complexité<br/>
dépend nécessairement des deux paramètres.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$O(2^n)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette complexité correspond à l'approche<br/>
par recherche exhaustive (essayer tous les<br/>
sous-ensembles d'objets). La programmation<br/>
dynamique est bien plus rapide.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>$O(n \cdot W)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>On remplit un tableau à deux dimensions de<br/>
taille $n \times W$. Pour chaque case, le<br/>
calcul est en $O(1)$. La complexité totale<br/>
est donc $O(nW)$.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q14 : Sac à dos : comparer les deux approches</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Pour le problème du sac à dos $0/1$,<br/>
l'algorithme glouton qui prend les objets par<br/>
ordre décroissant du ratio « valeur / poids »<br/>
donne-t-il toujours la solution optimale ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Le sac à dos $0/1$ ne possède <strong>pas</strong> la<br/>
propriété de choix glouton. Il faut donc<br/>
explorer plusieurs possibilités, ce que fait la<br/>
programmation dynamique en $O(nW)$, à comparer<br/>
aux $O(2^n)$ de la recherche exhaustive.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>L'algorithme glouton est plus rapide mais moins précis</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Il s'agit ici d'optimum exact, pas<br/>
d'approximation. Soit l'algorithme garantit<br/>
l'optimum, soit il ne le garantit pas. Il<br/>
n'y a pas de notion de précision<br/>
intermédiaire.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Non, l'algorithme glouton peut manquer l'optimum dans le cas $0/1$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Pour le sac à dos $0/1$, l'algorithme<br/>
glouton peut être trompé. Avec un sac de<br/>
capacité $50$ et les objets<br/>
$\{(60, 10), (100, 20), (120, 30)\}$<br/>
(valeur, poids), l'algorithme glouton prend<br/>
$(120, 30)$ en premier, alors que la<br/>
combinaison optimale est<br/>
$(60, 10) + (100, 20)$, pour une valeur<br/>
totale supérieure.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Oui, c'est l'algorithme correct pour ce problème</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette approche gloutonne est optimale pour<br/>
le <strong>sac à dos fractionnaire</strong> (où l'on<br/>
peut couper un objet), mais pas pour la<br/>
version $0/1$, où chaque objet est pris<br/>
entièrement ou pas du tout.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Cela dépend du langage de programmation utilisé</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>La propriété est purement mathématique. Elle<br/>
ne dépend pas du langage choisi pour<br/>
l'implémentation.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q15 : Approche descendante et approche ascendante</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Quelle est la différence entre l'approche<br/>
<strong>descendante</strong> et l'approche <strong>ascendante</strong> en<br/>
programmation dynamique ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>L'approche descendante (mémoïsation) est plus<br/>
naturelle quand la définition du problème est<br/>
récursive. L'approche ascendante (tabulation)<br/>
est en général plus économe en mémoire et plus<br/>
rapide en pratique. Le choix dépend du<br/>
contexte.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>L'approche descendante est récursive avec mémoïsation, l'approche ascendante est itérative avec tabulation</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>L'approche descendante part du problème<br/>
global et descend dans les sous-problèmes,<br/>
en utilisant un cache. L'approche<br/>
ascendante part des cas de base et<br/>
construit progressivement la table des<br/>
résultats jusqu'au problème global.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>L'approche descendante concerne les graphes, l'approche ascendante concerne les listes</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Les deux approches s'appliquent à toutes<br/>
les structures de données. La distinction<br/>
n'a rien à voir avec le type des éléments<br/>
manipulés.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>L'approche descendante est toujours plus rapide</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>En pratique, l'approche ascendante est<br/>
souvent <strong>plus rapide</strong>, car elle évite le<br/>
surcoût des appels récursifs. La complexité<br/>
asymptotique est cependant la même dans les<br/>
deux cas.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Aucune, ces termes sont synonymes</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Ce sont deux approches distinctes, même si<br/>
elles permettent toutes deux de résoudre<br/>
les mêmes problèmes.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q16 : Le décorateur `lru_cache` en Python</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>En Python, quel décorateur de la bibliothèque<br/>
<code>functools</code> automatise la mémoïsation d'une<br/>
fonction ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Le sigle LRU signifie « <em>Least Recently<br/>
Used</em> », soit « le moins récemment utilisé ».<br/>
Le cache stocke un nombre limité de résultats<br/>
($128$ par défaut, ou un nombre illimité avec<br/>
<code>maxsize=None</code>) et expulse les moins<br/>
récemment consultés.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p><code>@dynamic</code></p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Ce décorateur n'existe pas dans la<br/>
bibliothèque standard de Python.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p><code>@cache_me</code></p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Ce nom de décorateur n'existe pas en<br/>
Python. Le décorateur attendu se trouve<br/>
dans le module <code>functools</code>.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p><code>@lru_cache</code></p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Le décorateur<br/>
<code>@functools.lru_cache(maxsize=None)</code> ajoute<br/>
automatiquement un cache à n'importe<br/>
quelle fonction. C'est très pratique pour<br/>
des fonctions récursives, sans avoir à<br/>
coder soi-même la mémoïsation.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p><code>@memoize</code></p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Le décorateur <code>@memoize</code> n'est pas dans la<br/>
bibliothèque standard. Certaines<br/>
bibliothèques tierces le proposent, mais le<br/>
standard Python est <code>lru_cache</code>.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q17 : Rendu de monnaie en programmation dynamique</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Pour le <strong>rendu de monnaie</strong> avec un système de<br/>
pièces arbitraire, la programmation dynamique<br/>
calcule, en $O(n \cdot s)$ (où $s$ est la somme<br/>
à rendre et $n$ le nombre de pièces) :</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Le rendu de monnaie est l'exemple emblématique<br/>
d'un problème où l'algorithme glouton <strong>échoue</strong><br/>
sur certains systèmes monétaires (par exemple<br/>
$\{1, 3, 4\}$), alors que la programmation<br/>
dynamique garantit l'optimum.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La somme totale rendue</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>La somme à rendre est donnée en entrée du<br/>
problème, elle n'est pas calculée par<br/>
l'algorithme.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Le nombre minimal de pièces nécessaires pour atteindre la somme</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Pour chaque sous-somme $k \in [0, s]$,<br/>
l'algorithme calcule le nombre minimal de<br/>
pièces nécessaires. La récurrence utilisée<br/>
est<br/>
$\text{ND}(k) = 1 + \min_{p}\, \text{ND}(k - p)$,<br/>
où $p$ parcourt les pièces disponibles.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La pièce la plus grande utilisée</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette description correspond plutôt à<br/>
l'approche gloutonne, qui choisit toujours<br/>
la plus grande pièce disponible.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Une séquence ordonnée de pièces</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>La programmation dynamique calcule un<br/>
nombre minimal, et non une séquence<br/>
ordonnée. La séquence des pièces<br/>
effectivement utilisées peut être<br/>
reconstruite ensuite, par un parcours à<br/>
rebours du tableau.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q18 : Programmation dynamique et diviser pour régner</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>En quoi la programmation dynamique se<br/>
distingue-t-elle du paradigme « diviser pour<br/>
régner » classique ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Pour identifier si un problème relève de la<br/>
programmation dynamique, on peut tracer<br/>
l'arbre des appels récursifs et chercher les<br/>
nœuds dupliqués. S'il y en a beaucoup, la<br/>
programmation dynamique est probablement le<br/>
bon outil.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Le paradigme « diviser pour régner » utilise toujours la récursivité, contrairement à la programmation dynamique</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Les deux paradigmes peuvent utiliser la<br/>
récursivité. La programmation dynamique<br/>
peut aussi être itérative (par tabulation),<br/>
mais ce n'est pas la différence<br/>
essentielle.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La programmation dynamique ne s'applique qu'aux problèmes portant sur des entiers</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>La programmation dynamique s'applique à<br/>
tout problème possédant les deux propriétés<br/>
requises (sous-structure optimale et<br/>
sous-problèmes recouvrants), quel que soit<br/>
le type des données manipulées.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La programmation dynamique est plus simple à coder</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>C'est subjectif et souvent l'inverse. Les<br/>
algorithmes de type « diviser pour régner »<br/>
sont en général plus simples à concevoir.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>En programmation dynamique, les sous-problèmes se recouvrent ; en « diviser pour régner », ils sont disjoints</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>C'est la distinction fondamentale. Sans<br/>
recouvrement, la mémoïsation n'apporte<br/>
rien. Avec recouvrement, elle apporte des<br/>
gains majeurs.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q19 : Compromis mémoire et temps</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>La programmation dynamique illustre un<br/>
compromis classique en algorithmique. Lequel ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Pour la suite de Fibonacci, la version naïve a<br/>
une complexité en $O(2^n)$ en temps avec<br/>
$O(n)$ d'espace de pile. La version mémoïsée<br/>
passe à $O(n)$ en temps avec $O(n)$ de mémoire :<br/>
on a légèrement augmenté l'espace, mais on a<br/>
divisé le temps par un facteur exponentiel.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Moins de mémoire consommée et moins de temps de calcul</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Il n'y a pas de gain gratuit. Ne stocker<br/>
aucune valeur intermédiaire reviendrait au<br/>
cas naïf récursif, qui est précisément<br/>
beaucoup plus lent.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Plus de mémoire consommée, en échange de beaucoup moins de temps de calcul</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>On stocke les solutions intermédiaires<br/>
(mémoire en plus) pour ne plus avoir à les<br/>
recalculer (temps en moins). C'est un<br/>
compromis central en algorithmique.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Aucun changement par rapport à l'algorithme naïf</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>La programmation dynamique apporte un gain<br/>
majeur sur de nombreux problèmes (suite de<br/>
Fibonacci, sac à dos, plus longue<br/>
sous-séquence commune, etc.).</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Plus de temps de calcul mais moins de mémoire consommée</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>C'est exactement l'inverse. La<br/>
programmation dynamique consomme plus de<br/>
mémoire, mais elle accélère<br/>
considérablement le calcul.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q20 : Lecture d'une programmation dynamique ascendante</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Que calcule la fonction Python suivante ?</p>
<pre><code>def f(n):
    T = [0] * (n + 1)
    T[0] = 1
    for i in range(1, n + 1):
        T[i] = T[i-1] * i
    return T[n]</code></pre>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cet exemple illustre une <strong>programmation<br/>
dynamique ascendante</strong> très simple : on<br/>
remplit un tableau en partant du cas de base.<br/>
C'est l'archétype de la tabulation pour une<br/>
récurrence simple.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>La factorielle $n!$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>On a $T[i] = T[i-1] \cdot i = i!$, en<br/>
partant de $T[0] = 1 = 0!$. La fonction<br/>
renvoie donc bien $n!$, par construction<br/>
ascendante.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La $n$-ième valeur de la suite de Fibonacci</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>La suite de Fibonacci utiliserait<br/>
l'instruction <code>T[i] = T[i-1] + T[i-2]</code>,<br/>
fondée sur une addition de deux termes<br/>
précédents, et non sur la multiplication<br/>
par $i$.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$2^n$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Pour obtenir $2^n$, il faudrait écrire<br/>
<code>T[i] = T[i-1] <em> 2</code> plutôt que<br/>
<code>T[i-1] </em> i</code>.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La somme $1 + 2 + \cdots + n$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Une somme s'obtiendrait par addition. Or<br/>
la fonction effectue une multiplication<br/>
(<code>T[i-1] * i</code>), qui correspond à un<br/>
produit cumulé.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q21 : Plus longue sous-séquence commune</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Pour calculer la plus longue sous-séquence<br/>
commune à deux chaînes de longueurs $m$ et $n$,<br/>
la programmation dynamique a une complexité<br/>
de :</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cet algorithme est utilisé en bio-informatique<br/>
(alignement de séquences ADN), dans le<br/>
versionnage de fichiers (commande <code>diff</code>), en<br/>
reconnaissance vocale, et dans bien d'autres<br/>
domaines. La complexité quadratique reste<br/>
raisonnable même pour des chaînes de plusieurs<br/>
milliers de caractères.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>$O(m \cdot n)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>On construit un tableau à deux dimensions<br/>
de taille $m \times n$. Pour chaque case,<br/>
le calcul est en $O(1)$. La complexité<br/>
totale est donc $O(mn)$.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$O(m^2 + n^2)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>La complexité de ce problème est<br/>
multiplicative dans les deux longueurs, et<br/>
non additive sur leurs carrés.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$O(m + n)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette complexité linéaire correspondrait à<br/>
un simple parcours conjoint des deux<br/>
chaînes. Or il faut explorer les<br/>
combinaisons possibles, ce qui demande un<br/>
coût supérieur.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$O(2^{m+n})$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette complexité exponentielle correspond<br/>
à l'approche par recherche exhaustive<br/>
(essayer toutes les sous-séquences). La<br/>
programmation dynamique réduit ce coût à<br/>
un produit polynomial.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q22 : Piège du paramètre par défaut mutable</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Dans une mémoïsation manuelle, on écrit :</p>
<pre><code>def f(n, memo={}):
    if n in memo: return memo[n]
    if n &lt;= 1: return n
    memo[n] = f(n-1, memo) + f(n-2, memo)
    return memo[n]</code></pre>
<p>Quel problème pose le <code>memo={}</code> placé en<br/>
paramètre par défaut ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>La bonne pratique consiste à utiliser<br/>
<code>memo=None</code> puis, à l'intérieur de la<br/>
fonction, <code>if memo is None: memo = {}</code>. On peut<br/>
aussi recourir à <code>@functools.lru_cache</code>, qui<br/>
gère ces subtilités automatiquement.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Il est partagé entre tous les appels successifs de la fonction, conservant les valeurs entre exécutions différentes</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Python évalue les valeurs par défaut<br/>
<strong>une seule fois</strong>, à la définition de la<br/>
fonction. Le dictionnaire <code>memo</code> est donc<br/>
partagé. Cela peut être souhaité (cache<br/>
global) ou provoquer des bugs subtils si<br/>
l'on attendait un cache neuf à chaque<br/>
appel.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Il rend la fonction trop lente, car le dictionnaire est recréé à chaque appel</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>C'est l'inverse. Le dictionnaire par<br/>
défaut est créé <strong>une seule fois</strong> (à la<br/>
définition de la fonction), ce qui est<br/>
précisément le piège.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Il ralentit globalement Python</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Aucun ralentissement global. Le seul<br/>
problème est le partage non intentionnel<br/>
du cache entre les appels.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Il provoque une erreur de récursion</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Aucune erreur de récursion n'est causée par<br/>
un paramètre mutable par défaut. Le piège<br/>
est sémantique, et ne se manifeste pas par<br/>
une erreur d'exécution.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q23 : Reconstruire la solution</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>En programmation dynamique, on calcule souvent<br/>
une <strong>valeur optimale</strong> (par exemple le coût<br/>
minimal). Comment retrouve-t-on la <strong>solution<br/>
effective</strong>, c'est-à-dire la séquence de<br/>
décisions qui mène à cet optimum ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cette technique de reconstruction à rebours<br/>
est universelle en programmation dynamique :<br/>
après avoir rempli la table, on retrace les<br/>
choix faits à chaque étape pour reconstruire<br/>
la solution complète.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>On ne peut pas, la programmation dynamique ne donne que la valeur optimale</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>La solution effective peut tout à fait<br/>
être reconstruite : c'est ce qui rend la<br/>
programmation dynamique utile dans les<br/>
applications réelles.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>On lance plusieurs programmations dynamiques avec différents paramètres pour identifier les choix</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette approche serait inefficace. Une seule<br/>
exécution suffit, à condition de bien lire<br/>
la table à rebours.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>On la reconstruit en parcourant la table à rebours, à partir de la valeur finale</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>On remonte dans la table en suivant les<br/>
choix qui ont mené à chaque valeur<br/>
optimale. Cela permet de retrouver la<br/>
séquence de décisions sans alourdir le<br/>
calcul lui-même.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>On stocke aussi la solution dans la table</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>C'est possible mais coûteux en mémoire, et<br/>
souvent inutile : la solution se<br/>
reconstruit toujours en $O(n)$ après le<br/>
calcul, par un parcours à rebours.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q24 : Optimisation de l'espace</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Pour calculer la suite de Fibonacci en $O(n)$<br/>
en temps avec une mémoire réduite, on peut<br/>
écrire :</p>
<pre><code>a, b = 0, 1
for _ in range(n):
    a, b = b, a + b</code></pre>
<p>Quelle est la complexité <strong>en mémoire</strong> de<br/>
cette version ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cette optimisation est très courante. Pour de<br/>
nombreuses récurrences (suite de Fibonacci,<br/>
sac à dos selon une dimension, etc.), on peut<br/>
éviter de stocker toute la table en ne<br/>
conservant que les valeurs strictement<br/>
nécessaires.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$O(n)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>On ne stocke que deux variables, <code>a</code> et<br/>
<code>b</code>, durant tout le calcul. Aucun tableau<br/>
de taille $n$ n'est utilisé.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$O(n^2)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Aucune raison n'amène à une mémoire<br/>
quadratique pour ce calcul.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>$O(1)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Seules deux variables suffisent. C'est<br/>
l'optimisation classique : quand la<br/>
récurrence ne dépend que des $k$ valeurs<br/>
précédentes (ici $k = 2$), on peut réduire<br/>
l'espace à $O(k)$, soit une mémoire<br/>
constante.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$O(\log n)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Aucune structure logarithmique n'est<br/>
utilisée ici, simplement deux variables.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q25 : Reconnaître un problème de programmation dynamique</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Pour qu'un problème se résolve efficacement par<br/>
programmation dynamique, il doit posséder :</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Ces deux propriétés sont l'identité de la<br/>
programmation dynamique. Sans la sous-structure<br/>
optimale, il est impossible de combiner les<br/>
solutions. Sans le recouvrement des<br/>
sous-problèmes, la mémoïsation est inutile, et<br/>
l'on retombe sur le paradigme « diviser pour<br/>
régner » classique.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Une décomposition logarithmique du problème</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Cette caractéristique correspond plutôt au<br/>
paradigme « diviser pour régner »<br/>
classique. Ce n'est pas un prérequis de la<br/>
programmation dynamique.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Un seul cas de base et une seule récurrence</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Le nombre de cas de base ou de récurrences<br/>
n'est pas un critère. Ce sont les<br/>
propriétés structurelles du problème qui<br/>
comptent.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Une fonction objectif uniquement minimale</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>La programmation dynamique s'applique aussi<br/>
à des problèmes de maximisation (sac à dos,<br/>
plus longue sous-séquence) ou de comptage<br/>
(nombre de chemins).</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>La sous-structure optimale et des sous-problèmes recouvrants</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Ces deux propriétés sont nécessaires. La<br/>
sous-structure optimale permet de combiner<br/>
les solutions, et les sous-problèmes<br/>
recouvrants rendent la mémoïsation utile.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q26 : Chemins dans une grille</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>On veut compter le nombre de chemins<br/>
monotones (n'allant qu'à droite ou en bas)<br/>
d'un coin d'une grille rectangulaire de $m$<br/>
lignes et $n$ colonnes au coin opposé. Quelle<br/>
est la récurrence appropriée et la complexité<br/>
par programmation dynamique ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Ce problème est l'un des plus simples<br/>
illustrant la programmation dynamique sur une<br/>
table à deux dimensions. La même technique<br/>
s'étend aux variantes (cases bloquées,<br/>
sommes minimales, etc.).</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>$C(i, j) = C(i-1, j) + C(i, j-1)$ avec<br/>
$C(0, j) = C(i, 0) = 1$, complexité<br/>
$O(m \cdot n)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : pour atteindre la case<br/>
$(i, j)$, on vient soit du dessus<br/>
$(i-1, j)$ soit de la gauche $(i, j-1)$.<br/>
Les cas de base sont les bords, où il<br/>
n'existe qu'un seul chemin (descente ou<br/>
déplacement à droite uniquement). On<br/>
remplit un tableau à deux dimensions de<br/>
taille $m \times n$, chaque case en<br/>
$O(1)$, donc complexité totale<br/>
$O(m \cdot n)$.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$C(i, j) = C(i-1, j-1) + 1$, complexité<br/>
$O(m \cdot n)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la récurrence proposée ne<br/>
correspond pas au problème. On ne peut se<br/>
déplacer qu'à droite <strong>ou</strong> vers le bas,<br/>
pas en diagonale. La récurrence correcte<br/>
additionne les chemins venant de la case<br/>
du dessus et de la case de gauche.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$C(i, j) = C(i-1, j) \cdot C(i, j-1)$,<br/>
complexité $O(m + n)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : pour compter le nombre de<br/>
chemins, on <strong>additionne</strong> les<br/>
possibilités, on ne les multiplie pas. La<br/>
complexité est aussi sous-estimée :<br/>
remplir un tableau à deux dimensions<br/>
coûte $O(m \cdot n)$, pas $O(m + n)$.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$C(i, j) = i + j$, calcul direct en<br/>
$O(1)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : le nombre de chemins n'est pas la<br/>
somme des coordonnées mais bien le<br/>
coefficient binomial $\binom{i+j}{i}$. Une<br/>
formule fermée existe, mais elle se<br/>
calcule en $O(\min(i, j))$ multiplications,<br/>
pas en temps constant.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q27 : Distance d'édition</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>La <strong>distance d'édition</strong> (ou distance de<br/>
Levenshtein) entre deux chaînes mesure le<br/>
nombre minimal d'opérations<br/>
(insertion, suppression, substitution) pour<br/>
transformer l'une en l'autre. Quel est le<br/>
schéma de programmation dynamique pour la<br/>
calculer ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>La distance d'édition est l'algorithme central<br/>
de la commande <code>diff</code>, des correcteurs<br/>
orthographiques, et de la bio-informatique<br/>
(alignement de séquences). C'est un exemple<br/>
type de programmation dynamique sur une<br/>
table à deux dimensions.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$D(i, j) = \max(i, j)$, calcul en $O(1)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur grossière : ce serait dire que la<br/>
distance d'édition est toujours la<br/>
longueur de la chaîne la plus longue. Or<br/>
deux chaînes identiques ont une distance<br/>
de zéro, et la formule donnée n'en tient<br/>
pas compte.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>$D(i, j) = D(i-1, j-1)$ si les deux<br/>
derniers caractères sont égaux ; sinon,<br/>
$D(i, j) = 1 + \min(D(i-1, j),<br/>
D(i, j-1), D(i-1, j-1))$. Complexité<br/>
$O(m \cdot n)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : trois opérations possibles<br/>
se traduisent par trois récurrences<br/>
(suppression, insertion, substitution),<br/>
plus le cas où les caractères coïncident.<br/>
Cas de base : $D(0, j) = j$ et<br/>
$D(i, 0) = i$. La complexité est<br/>
quadratique, ce qui reste praticable pour<br/>
des chaînes de quelques milliers de<br/>
caractères.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>On compare uniquement les premiers<br/>
caractères des deux chaînes. Complexité<br/>
$O(1)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la distance d'édition prend en<br/>
compte <strong>toutes</strong> les positions, pas<br/>
seulement la première. Une approche<br/>
$O(1)$ ne donnerait évidemment pas le<br/>
résultat correct.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$D(i, j) = D(i-1, j-1) + 1$ dans tous les<br/>
cas. Complexité $O(\min(m, n))$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : cette récurrence ne traite pas<br/>
séparément le cas où les caractères<br/>
coïncident (gratuit). Elle ignore aussi<br/>
les opérations d'insertion et de<br/>
suppression, qui modifient la longueur<br/>
des chaînes. Le résultat serait<br/>
systématiquement faux.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q28 : Trace sur le rendu de monnaie</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Avec les pièces $\{1, 3, 4\}$ et la somme à<br/>
rendre $s = 6$, quel est le <strong>nombre minimal</strong><br/>
de pièces calculé par programmation dynamique<br/>
(et l'écart avec la méthode gloutonne qui<br/>
prend toujours la plus grosse pièce<br/>
possible) ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cet exemple est emblématique : il prouve<br/>
qu'avec un système monétaire arbitraire,<br/>
l'algorithme glouton n'est plus optimal. Avec<br/>
les systèmes monétaires usuels (euros,<br/>
dollars), l'algorithme glouton fonctionne<br/>
grâce à des propriétés structurelles<br/>
particulières des dénominations choisies.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La programmation dynamique trouve $1$<br/>
pièce, l'algorithme glouton en trouve $2$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : aucune pièce de valeur $6$<br/>
n'existe dans le système monétaire<br/>
$\{1, 3, 4\}$, donc une pièce ne peut pas<br/>
suffire. Le minimum atteignable est de<br/>
deux pièces (combinaison $3 + 3$).</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La programmation dynamique et l'algorithme<br/>
glouton trouvent tous deux $3$ pièces</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : si c'était le cas, la<br/>
programmation dynamique n'aurait aucun<br/>
intérêt sur ce problème. Refaire le<br/>
calcul : avec $3 + 3$, deux pièces<br/>
suffisent, ce que l'algorithme glouton<br/>
rate.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Les deux échouent car aucune combinaison<br/>
ne donne $6$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la combinaison $3 + 3 = 6$ est<br/>
parfaitement valide. De même<br/>
$1 + 1 + 4 = 6$ ou $1 + 1 + 1 + 3 = 6$<br/>
fonctionnent. Le problème est résoluble.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>La programmation dynamique trouve $2$<br/>
pièces (la combinaison $3 + 3$), tandis<br/>
que l'algorithme glouton en utilise $3$<br/>
(la combinaison $4 + 1 + 1$)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : c'est l'exemple<br/>
pédagogique classique où la stratégie<br/>
gloutonne échoue à atteindre l'optimum.<br/>
Glouton : prendre $4$, reste $2$, prendre<br/>
$1$, prendre $1$, total $3$ pièces.<br/>
Programmation dynamique : explore toutes<br/>
les combinaisons et trouve $3 + 3 = 6$,<br/>
soit seulement $2$ pièces. Cas<br/>
paradigmatique de l'intérêt de la<br/>
programmation dynamique.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q29 : Trace du sac à dos</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>On dispose d'un sac de capacité $W = 5$ et de<br/>
trois objets : $O_1$ (poids $2$, valeur $3$),<br/>
$O_2$ (poids $3$, valeur $4$), $O_3$ (poids<br/>
$4$, valeur $5$). Quelle est la valeur<br/>
optimale obtenue par programmation<br/>
dynamique ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Construire le tableau $V$ à la main est un<br/>
excellent exercice. Il révèle que la<br/>
programmation dynamique « teste » toutes les<br/>
combinaisons utiles sans en énumérer<br/>
explicitement aucune. Pour reconstruire la<br/>
solution (les objets choisis), on remonte<br/>
dans le tableau à rebours en regardant<br/>
pour chaque case quelle décision a mené à<br/>
l'optimum.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$12$ (en prenant les trois objets)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : prendre les trois objets pèse<br/>
$2 + 3 + 4 = 9 \mathrm{kg}$, bien<br/>
au-delà de la capacité du sac. C'est la<br/>
difficulté du sac à dos : on doit<br/>
renoncer à des objets précieux à cause<br/>
de la contrainte de poids.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>$7$ (en prenant $O_1$ et $O_2$, poids<br/>
total $5$, valeur totale $7$)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : on construit la table<br/>
$V[i][w]$ donnant la valeur optimale en<br/>
considérant les $i$ premiers objets pour<br/>
une capacité $w$. Quelques valeurs clés :<br/>
$V[2][5] = \max(V[1][5], V[1][2] + 4)$<br/>
$= \max(3, 3 + 4) = 7$. À l'arrivée<br/>
$V[3][5] = \max(V[2][5], V[2][1] + 5)$<br/>
$= \max(7, 0 + 5) = 7$. La solution<br/>
optimale prend $O_1$ ($2 \mathrm{kg}$,<br/>
$3$) et $O_2$ ($3 \mathrm{kg}$, $4$),<br/>
poids total $5$, valeur totale $7$.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$5$ (en prenant uniquement $O_3$)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : prendre seulement $O_3$ ne donne<br/>
que $5$ de valeur, alors que la<br/>
combinaison $O_1 + O_2$ donne $7$ pour<br/>
un poids identique au sac (capacité<br/>
atteinte exactement). Il vaut donc mieux<br/>
prendre les deux petits objets que le<br/>
gros.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>$9$ (en prenant $O_2$ et $O_3$)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la combinaison $O_2$ + $O_3$<br/>
pèse $3 + 4 = 7 \mathrm{kg}$, ce qui<br/>
dépasse la capacité $W = 5$ du sac.<br/>
Cette solution n'est donc pas réalisable.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q30 : Code du sac à dos en programmation dynamique</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Quel code Python implémente correctement la<br/>
programmation dynamique ascendante (par<br/>
tabulation) pour le sac à dos $0/1$ avec $n$<br/>
objets de poids <code>poids[i]</code> et valeurs<br/>
<code>valeurs[i]</code> et un sac de capacité <code>W</code> ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Complexité : $O(n \cdot W)$ en temps et en<br/>
espace. Optimisation possible : ne conserver<br/>
que la ligne courante et la précédente,<br/>
ramenant l'espace à $O(W)$. Pour<br/>
reconstruire la solution effective, on<br/>
remonte le tableau de la case $V[n][W]$ vers<br/>
la case $V[0][0]$, en notant à chaque<br/>
étape si l'objet $i$ a été pris.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<pre><code>def sac_a_dos(poids, valeurs, W):
    total = 0
    for i in range(len(poids)):
        total += valeurs[i]
    return total</code></pre>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : ce code retourne la somme de<br/>
<strong>toutes</strong> les valeurs sans tenir compte<br/>
de la contrainte de poids. Il<br/>
renverrait par exemple $12$ pour le cas<br/>
de la question précédente, alors que<br/>
la vraie réponse est $7$.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<pre><code>def sac_a_dos(poids, valeurs, W):
    n = len(poids)
    V = [[0] * (W + 1) for _ in range(n + 1)]
    for i in range(1, n + 1):
        for w in range(W + 1):
            if poids[i-1] &lt;= w:
                V[i][w] = max(V[i-1][w],
                              V[i-1][w-poids[i-1]] + valeurs[i-1])
            else:
                V[i][w] = V[i-1][w]
    return V[n][W]</code></pre>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : on construit un tableau à<br/>
deux dimensions de taille<br/>
$(n+1) \times (W+1)$. Pour chaque case<br/>
$(i, w)$ : si l'objet $i$ rentre dans le<br/>
sac (<code>poids[i-1] &lt;= w</code>), on prend le<br/>
maximum entre « ne pas le prendre »<br/>
(<code>V[i-1][w]</code>) et « le prendre »<br/>
(<code>V[i-1][w-poids[i-1]] + valeurs[i-1]</code>) ;<br/>
sinon, on ne peut pas le prendre. Indices<br/>
$i-1$ car les listes Python commencent<br/>
à $0$.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<pre><code>def sac_a_dos(poids, valeurs, W):
    # Tri par ratio valeur/poids décroissant
    ordre = sorted(range(len(poids)),
                   key=lambda i: -valeurs[i]/poids[i])
    total = 0
    capa = W
    for i in ordre:
        if poids[i] &lt;= capa:
            total += valeurs[i]
            capa -= poids[i]
    return total</code></pre>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : c'est l'algorithme <strong>glouton</strong> par<br/>
ratio valeur/poids. Il est optimal pour<br/>
le sac à dos <strong>fractionnaire</strong> (où l'on<br/>
peut couper des objets), mais pas pour<br/>
la version $0/1$. Il peut renvoyer une<br/>
valeur sous-optimale, comme on l'a vu<br/>
dans la question précédente.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<pre><code>def sac_a_dos(poids, valeurs, W):
    return max(valeurs)</code></pre>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : ce code renvoie la valeur de<br/>
l'objet le plus précieux, sans même<br/>
vérifier qu'il rentre dans le sac. Et<br/>
surtout, il ignore les combinaisons<br/>
d'objets, qui sont souvent meilleures.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q31 : Mesurer le gain de la mémoïsation</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>On compare le calcul de <code>fibonacci(40)</code> avec<br/>
la version récursive naïve et avec la<br/>
version mémoïsée. Quel est l'ordre de<br/>
grandeur du rapport entre les deux<br/>
complexités ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cet exemple illustre la puissance de la<br/>
programmation dynamique. Bien sûr, pour<br/>
Fibonacci en pratique, on utilise plutôt la<br/>
version itérative à deux variables, qui a<br/>
la même complexité $O(n)$ mais avec une<br/>
consommation mémoire $O(1)$ au lieu de<br/>
$O(n)$.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Les deux versions ont la même complexité<br/>
asymptotique</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la mémoïsation change<br/>
fondamentalement la complexité. La<br/>
version naïve calcule plusieurs fois<br/>
les mêmes valeurs (par exemple,<br/>
$\mathrm{fib}(35)$ est calculée<br/>
plusieurs millions de fois) ; la<br/>
mémoïsée ne calcule chaque valeur qu'une<br/>
seule fois.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La version mémoïsée est environ deux fois<br/>
plus rapide</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : un facteur deux serait une<br/>
amélioration mineure. Or le passage de<br/>
$O(2^n)$ à $O(n)$ représente un gain<br/>
<strong>exponentiel</strong>, pas multiplicatif. Pour<br/>
$n = 40$, c'est un facteur de plus de<br/>
dix milliards.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La version mémoïsée est plus lente à<br/>
cause du surcoût du cache</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : pour $n$ aussi petit que $20$,<br/>
le surcoût du cache est négligeable<br/>
devant le gain en évitant les calculs<br/>
redondants. Pour $n = 40$, le gain est<br/>
colossal.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>La version mémoïsée est environ<br/>
$10^{10}$ fois plus rapide</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : version naïve en<br/>
$O(2^n)$, soit environ<br/>
$2^{40} \approx 10^{12}$ appels<br/>
récursifs. Version mémoïsée en $O(n)$,<br/>
soit environ $40$ appels effectifs. Le<br/>
rapport est donc d'environ<br/>
$10^{12} / 40 \approx 2{,}5 \cdot 10^{10}$.<br/>
En pratique, on passe de plusieurs<br/>
minutes à une fraction de milliseconde.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Programmation dynamique — Q32 : Optimisation de l'espace pour le sac à dos</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Le sac à dos en programmation dynamique<br/>
utilise par défaut un tableau $V$ de taille<br/>
$(n+1) \times (W+1)$, soit un espace<br/>
$O(n \cdot W)$. Comment réduire cet<br/>
espace à $O(W)$ tout en gardant la même<br/>
complexité en temps ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Compromis : la réduction à $O(W)$ permet de<br/>
traiter des sacs à dos avec des capacités<br/>
$W$ très grandes (typiquement plusieurs<br/>
millions), au prix de la perte d'information<br/>
pour reconstruire la solution. Si on a<br/>
besoin des deux (faible mémoire <strong>et</strong><br/>
reconstruction), des techniques plus<br/>
avancées existent, comme le « partage par le<br/>
milieu » qui combine deux passes sur des<br/>
moitiés du tableau.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>On observe que pour calculer la ligne<br/>
$V[i]$, on n'a besoin que de la ligne<br/>
$V[i-1]$. On peut donc se contenter de<br/>
deux lignes (la précédente et la<br/>
courante), voire d'une seule en<br/>
parcourant les capacités de droite à<br/>
gauche</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : c'est une optimisation<br/>
classique. La récurrence ne dépend que<br/>
de la ligne précédente, donc deux<br/>
tableaux de taille $W+1$ suffisent.<br/>
Mieux : avec un seul tableau parcouru à<br/>
rebours (<code>for w in range(W, poids[i]-1,<br/>
-1)</code>), on garantit qu'on utilise bien<br/>
l'ancienne valeur de $V[w-poids[i]]$<br/>
avant qu'elle ne soit écrasée par la<br/>
ligne en cours. Espace final : $O(W)$.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>On utilise un dictionnaire au lieu d'un<br/>
tableau</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : un dictionnaire peut éviter de<br/>
stocker des cases inutilisées (avec une<br/>
mémoïsation paresseuse), mais dans le<br/>
pire cas, on stocke toujours $O(n<br/>
\cdot W)$ entrées. Le gain n'est pas<br/>
systématique.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>On ne peut pas réduire l'espace en<br/>
dessous de $O(n \cdot W)$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la réduction à $O(W)$ est<br/>
possible et constitue une optimisation<br/>
standard. Elle est cependant payée par<br/>
la perte de la trace permettant de<br/>
<strong>reconstruire</strong> la solution effective :<br/>
si l'on n'a plus que la ligne courante,<br/>
on ne peut plus remonter dans le<br/>
tableau à rebours.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>On compresse les valeurs avec un<br/>
algorithme de compression</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la compression peut réduire la<br/>
taille en mémoire d'un facteur constant,<br/>
mais ne change pas la complexité<br/>
asymptotique. L'optimisation correcte<br/>
est structurelle : ne stocker que ce<br/>
qui est utile pour le calcul courant.</p>]]></text>
    </feedback>
  </answer>
</question>

</quiz>
