{
  "chapter": {
    "id": "programmation-dynamique",
    "level": "terminale",
    "theme": "Algorithmique",
    "title": "Programmation dynamique",
    "description": "Sous-problèmes qui se chevauchent, sous-structure optimale,\nmémoïsation descendante, tabulation ascendante,\nexemples classiques (suite de Fibonacci, plus longue\nsous-séquence commune, sac à dos, distance d'édition,\nrendu de monnaie).",
    "parties": [
      {
        "id": "principe",
        "title": "Principe, sous-problèmes et comparaison"
      },
      {
        "id": "techniques",
        "title": "Mémoïsation et tabulation"
      },
      {
        "id": "exemples",
        "title": "Exemples classiques"
      }
    ],
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "definition"
      ],
      "title": "Idée centrale de la programmation dynamique",
      "partie": "principe",
      "statement": "Quel est le principe fondamental de la\nprogrammation dynamique ?",
      "options": [
        {
          "text": "Diviser le problème en deux moitiés et résoudre chacune indépendamment",
          "correct": false,
          "feedback": "Cette description correspond au paradigme\n« diviser pour régner ». La différence\nessentielle est que les sous-problèmes y sont\n**disjoints**, alors qu'en programmation\ndynamique ils se **recouvrent**.\n"
        },
        {
          "text": "Faire à chaque étape le choix qui semble localement le meilleur",
          "correct": false,
          "feedback": "Cette description correspond au principe des\nalgorithmes gloutons. La programmation\ndynamique, au contraire, explore toutes les\npossibilités de manière organisée, en\nmémorisant les solutions des sous-problèmes.\n"
        },
        {
          "text": "Modifier le programme à mesure qu'il s'exécute",
          "correct": false,
          "feedback": "Cette description évoque l'auto-modification\nde code, qui n'a aucun rapport avec la\nprogrammation dynamique. Le mot\n« dynamique » désigne ici le caractère\ntemporel de la décision, pas une\nmodification du programme.\n"
        },
        {
          "text": "Mémoriser les solutions des sous-problèmes",
          "correct": true,
          "feedback": "La programmation dynamique exploite la\nprésence de sous-problèmes qui se répètent\ndans la décomposition récursive. En les\nmémorisant, on évite la croissance\nexponentielle du nombre de recalculs.\n"
        }
      ],
      "explanation": "Le terme « programmation » dans « programmation\ndynamique » vient de l'optimisation linéaire\n(Bellman, années $1950$), et non de la\nprogrammation informatique. Il signifie « plan,\ntableau », ce qui justifie la notion de\ntabulation."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "memoisation"
      ],
      "title": "Mémoïsation",
      "partie": "techniques",
      "statement": "Qu'est-ce que la **mémoïsation** ?",
      "options": [
        {
          "text": "La comparaison de deux algorithmes pour choisir le plus rapide",
          "correct": false,
          "feedback": "Cette opération relève de la mesure de\nperformance, et n'a aucun rapport avec la\nmémoïsation.\n"
        },
        {
          "text": "La conversion automatique d'une fonction récursive en boucle itérative",
          "correct": false,
          "feedback": "Cette transformation s'appelle la\n**dérécursivation**, et c'est une technique\ndifférente. La mémoïsation conserve la\nstructure récursive de la fonction, en y\najoutant simplement un cache.\n"
        },
        {
          "text": "La compression d'un programme en mémoire pour gagner de la place",
          "correct": false,
          "feedback": "La mémoïsation n'a aucun rapport avec la\ntaille du programme. Elle concerne le\n**temps de calcul**, en évitant de\nrecalculer plusieurs fois la même valeur.\n"
        },
        {
          "text": "Stocker les résultats déjà calculés dans un cache",
          "correct": true,
          "feedback": "À chaque appel récursif, l'algorithme\nvérifie si le résultat est déjà connu\n(typiquement dans un dictionnaire ou un\ntableau) avant de le calculer. Si la valeur\nest déjà disponible, elle est renvoyée\ndirectement.\n"
        }
      ],
      "explanation": "Le mot « mémoïsation » associe les idées de\nmémoire et de récursivité. C'est l'approche\n« descendante » de la programmation dynamique :\non part du problème complet, on descend dans les\nsous-problèmes en mémorisant les résultats au\npassage."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "tabulation"
      ],
      "title": "Tabulation",
      "partie": "techniques",
      "statement": "Qu'appelle-t-on **tabulation** en programmation\ndynamique ?",
      "options": [
        {
          "text": "Le découpage d'un problème en sous-problèmes égaux",
          "correct": false,
          "feedback": "Cette description correspond au paradigme\n« diviser pour régner ». La tabulation\nconsiste à remplir un tableau ; elle ne\ndécoupe pas le problème.\n"
        },
        {
          "text": "La création d'une table HTML pour afficher les résultats",
          "correct": false,
          "feedback": "La « tabulation » dans ce contexte\nalgorithmique n'a aucun rapport avec une\nmise en forme HTML. Il s'agit du\nremplissage progressif d'un tableau de\nvaleurs.\n"
        },
        {
          "text": "Une optimisation des comparaisons utilisée dans un algorithme de tri",
          "correct": false,
          "feedback": "La tabulation est spécifique à la\nprogrammation dynamique. Elle n'a pas de\nrôle particulier dans les algorithmes de\ntri.\n"
        },
        {
          "text": "Remplir un tableau des petits aux grands sous-problèmes",
          "correct": true,
          "feedback": "C'est l'approche dite « ascendante » : on\npart des cas de base et on remplit\nprogressivement un tableau, sans aucune\nrécursion.\n"
        }
      ],
      "explanation": "La mémoïsation et la tabulation sont les deux\nfaces de la programmation dynamique : la\nmémoïsation est descendante (récursive, avec un\ncache) ; la tabulation est ascendante\n(itérative, avec un tableau). En pratique, la\ntabulation est souvent plus rapide, car elle\névite le surcoût des appels récursifs."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "fibonacci"
      ],
      "title": "Suite de Fibonacci en programmation dynamique",
      "partie": "exemples",
      "statement": "Quelle est la complexité de la suite de Fibonacci\ncalculée avec mémoïsation ?",
      "options": [
        {
          "text": "$O(n^2)$",
          "correct": false,
          "feedback": "La programmation dynamique appliquée à\nFibonacci ne nécessite qu'un parcours\nlinéaire des valeurs, et non une double\nitération qui justifierait une complexité\nquadratique.\n"
        },
        {
          "text": "$O(\\log n)$",
          "correct": false,
          "feedback": "Avec la récurrence $f(n) = f(n-1) + f(n-2)$,\nla mémoïsation calcule une fois chaque\nvaleur de $f(0)$ à $f(n)$ : le nombre de\ncalculs est proportionnel à $n$, et non à\n$\\log n$.\n"
        },
        {
          "text": "$O(2^n)$",
          "correct": false,
          "feedback": "Cette complexité correspond à la version\n**récursive naïve**, sans mémoïsation. Le\nrecours à un cache réduit drastiquement ce\ncoût.\n"
        },
        {
          "text": "$O(n)$",
          "correct": true,
          "feedback": "Avec mémoïsation, chaque valeur\n$\\text{fib}(k)$ pour $k$ allant de $0$ à\n$n$ n'est calculée qu'une seule fois, ce\nqui donne $n$ calculs au total.\n"
        }
      ],
      "explanation": "Le passage de $O(2^n)$ à $O(n)$ pour la suite\nde Fibonacci est l'exemple canonique de la\npuissance de la programmation dynamique. Pour\n$n = 50$, on passe ainsi de plus d'un million\nd'appels à seulement $51$."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "sous-problemes-chevauchent"
      ],
      "title": "Sous-problèmes qui se chevauchent",
      "partie": "principe",
      "statement": "Que signifie « le problème a des sous-problèmes\n**qui se chevauchent** » ?",
      "options": [
        {
          "text": "Les sous-problèmes ont tous la même solution",
          "correct": false,
          "feedback": "Les sous-problèmes peuvent avoir des\nsolutions très différentes, ce qui est\nd'ailleurs le cas le plus courant. Le\nchevauchement signifie qu'ils\n**apparaissent plusieurs fois** dans\nl'arbre des appels récursifs, et non qu'ils\npartagent leur solution.\n"
        },
        {
          "text": "Les sous-problèmes sont triés par taille décroissante",
          "correct": false,
          "feedback": "L'ordre dans lequel on considère les\nsous-problèmes n'a rien à voir avec leur\nchevauchement. Le mot « chevauchement »\nrenvoie à la notion de **doublon**, pas\nd'ordre.\n"
        },
        {
          "text": "Les sous-problèmes sont indépendants les uns des autres",
          "correct": false,
          "feedback": "Si les sous-problèmes étaient indépendants,\non appliquerait plutôt le paradigme\n« diviser pour régner ». La programmation\ndynamique exploite précisément le contraire.\n"
        },
        {
          "text": "Plusieurs sous-problèmes apparaissent plusieurs fois",
          "correct": true,
          "feedback": "C'est le cas typique de la suite de\nFibonacci, où la valeur $\\text{fib}(k)$ est\ncalculée plusieurs fois dans la version\nnaïve. La programmation dynamique évite\nprécisément ces recalculs.\n"
        }
      ],
      "explanation": "Cette propriété est essentielle pour que la\nprogrammation dynamique soit utile. Si les\nsous-problèmes sont uniques, la mémoïsation\nn'apporte rien et on retombe sur le paradigme\n« diviser pour régner » classique."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "sous-structure-optimale"
      ],
      "title": "Sous-structure optimale",
      "partie": "principe",
      "statement": "Que signifie « le problème possède une\n**sous-structure optimale** » ?",
      "options": [
        {
          "text": "La taille des sous-problèmes diminue de manière logarithmique",
          "correct": false,
          "feedback": "Cette description évoque la diminution\nlogarithmique du paradigme « diviser pour\nrégner » équilibré, mais elle ne correspond\npas à la définition de la sous-structure\noptimale.\n"
        },
        {
          "text": "L'optimum global se compose des optima des sous-problèmes",
          "correct": true,
          "feedback": "Cette propriété permet de construire la\nsolution optimale en combinant les solutions\noptimales des sous-problèmes. C'est elle qui\nrend la programmation dynamique applicable.\n"
        },
        {
          "text": "Le problème peut être résolu en parallèle sur plusieurs processeurs",
          "correct": false,
          "feedback": "La sous-structure optimale est une propriété\nmathématique du problème, et non une\ncaractéristique algorithmique. Le\nparallélisme relève de l'implémentation.\n"
        },
        {
          "text": "Les sous-problèmes ont tous strictement la même taille",
          "correct": false,
          "feedback": "Cette uniformité de taille n'est pas\nrequise. La sous-structure optimale parle de\nla composition des solutions, pas des\ntailles des sous-problèmes.\n"
        }
      ],
      "explanation": "La sous-structure optimale et le caractère\nchevauchement des sous-problèmes sont les deux\ningrédients qui rendent un problème adapté à la\nprogrammation dynamique. Sans la première, on\nne peut pas combiner les solutions ; sans la\nseconde, la mémoïsation ne sert à rien."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "comparaison-glouton"
      ],
      "title": "Programmation dynamique et algorithme glouton",
      "partie": "principe",
      "statement": "Quelle est la différence principale entre un\n**algorithme glouton** et un algorithme de\n**programmation dynamique** ?",
      "options": [
        {
          "text": "Le glouton choisit localement, la PD explore les sous-problèmes",
          "correct": true,
          "feedback": "L'algorithme glouton est plus rapide mais\npeut manquer l'optimum global. La\nprogrammation dynamique est plus coûteuse,\nmais elle garantit l'optimum global lorsque\nle problème possède la sous-structure\noptimale.\n"
        },
        {
          "text": "L'algorithme glouton est récursif, alors que la programmation dynamique est itérative",
          "correct": false,
          "feedback": "Les deux approches peuvent être implémentées\naussi bien récursivement qu'itérativement.\nC'est le **principe** qui les distingue, et\nnon leur forme syntaxique.\n"
        },
        {
          "text": "La programmation dynamique est utilisée uniquement pour les jeux vidéo",
          "correct": false,
          "feedback": "La programmation dynamique a un champ\nd'application très large : alignement de\nséquences, optimisation économique,\ntraitement automatique du langage, et bien\nd'autres domaines.\n"
        },
        {
          "text": "L'algorithme glouton ne fonctionne que sur les graphes",
          "correct": false,
          "feedback": "Les algorithmes gloutons s'appliquent à de\ntrès nombreux types de problèmes : rendu de\nmonnaie, planification d'activités, sac à\ndos fractionnaire, et bien d'autres.\n"
        }
      ],
      "explanation": "Quand un problème possède la **propriété de choix glouton** (un choix local mène à\nl'optimum global), un algorithme glouton suffit\net reste plus efficace. Sinon, la programmation\ndynamique est souvent nécessaire pour\natteindre l'optimum."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "memoisation",
        "structure"
      ],
      "title": "Structure de mémoïsation",
      "partie": "techniques",
      "statement": "Quelle structure Python convient le mieux pour\nmémoïser une fonction prenant un seul paramètre\nentier positif ?",
      "options": [
        {
          "text": "Une liste indexée par la valeur du paramètre",
          "correct": true,
          "feedback": "Pour des paramètres entiers positifs dans\nune plage connue, une liste donne un accès\nen $O(1)$ très efficace. C'est la structure\nla plus rapide.\n"
        },
        {
          "text": "Un fichier sur le disque",
          "correct": false,
          "feedback": "Le disque est beaucoup trop lent pour de la\nmémoïsation en mémoire. Il s'utiliserait\nplutôt pour persister un cache entre deux\nexécutions du programme.\n"
        },
        {
          "text": "Un dictionnaire avec les paramètres comme clés",
          "correct": false,
          "feedback": "Un dictionnaire fonctionne, mais avec un\nsurcoût lié au calcul des empreintes. Pour\ndes indices entiers, la liste reste plus\nrapide.\n"
        },
        {
          "text": "Une variable globale unique",
          "correct": false,
          "feedback": "Une seule variable ne peut stocker qu'une\nseule valeur, alors que la mémoïsation\ndemande d'en mémoriser plusieurs.\n"
        }
      ],
      "explanation": "Pour des paramètres entiers, la liste est\nidéale. Pour des paramètres plus complexes\n(chaînes, $p$-uplets), le dictionnaire\ndevient nécessaire."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "bellman"
      ],
      "title": "Origine du terme",
      "partie": "principe",
      "statement": "Le terme « programmation dynamique » a été\nintroduit en $1953$ par :",
      "options": [
        {
          "text": "Tim Berners-Lee",
          "correct": false,
          "feedback": "Tim Berners-Lee est l'inventeur du World\nWide Web. Son travail n'a aucun rapport\navec la programmation dynamique.\n"
        },
        {
          "text": "Alan Turing",
          "correct": false,
          "feedback": "Alan Turing est principalement connu pour\nla machine de Turing et le décryptage\nd'Enigma, mais pas pour la programmation\ndynamique.\n"
        },
        {
          "text": "Donald Knuth",
          "correct": false,
          "feedback": "Donald Knuth est connu pour son traité\nmonumental « The Art of Computer\nProgramming », mais ce n'est pas lui qui\nest à l'origine de la programmation\ndynamique.\n"
        },
        {
          "text": "Richard Bellman",
          "correct": true,
          "feedback": "Richard Bellman a inventé la programmation\ndynamique pour traiter des problèmes\nd'optimisation séquentielle. Le mot\n« programmation » vient du jargon militaire\nde l'époque, dans le sens de\n« planification ».\n"
        }
      ],
      "explanation": "Bellman a expliqué qu'il avait choisi le terme\n« dynamique » pour évoquer le caractère\ntemporel de la décision, et « programmation »\nau sens de planification. L'équation de Bellman\nreste aujourd'hui un outil central en\noptimisation et en apprentissage par\nrenforcement."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "exemples"
      ],
      "title": "Exemple classique de programmation dynamique",
      "partie": "principe",
      "statement": "Lequel des problèmes suivants se résout\ntypiquement par programmation dynamique ?",
      "options": [
        {
          "text": "Tester si un nombre est premier",
          "correct": false,
          "feedback": "Les tests de primalité ne se prêtent pas à\nla programmation dynamique, car ils ne font\npas apparaître naturellement de\nsous-problèmes qui se chevauchent.\n"
        },
        {
          "text": "Inverser une chaîne de caractères",
          "correct": false,
          "feedback": "C'est une opération en $O(n)$ qui ne\nrequiert aucune mémoïsation. Aucune\ndécomposition en sous-problèmes ne\ns'impose.\n"
        },
        {
          "text": "Trier un très grand tableau",
          "correct": false,
          "feedback": "Les algorithmes de tri usuels (tri par\ninsertion, par sélection, par fusion) ne\nrelèvent pas de la programmation\ndynamique.\n"
        },
        {
          "text": "Rendre la monnaie avec le moins de pièces",
          "correct": true,
          "feedback": "Le nombre minimal de pièces pour une\nsomme $s$ se déduit des sommes plus\npetites $s - p$, pour chaque pièce $p$.\nCes sous-problèmes se chevauchent : on\nmémorise leurs résultats dans un\ntableau pour ne les calculer qu'une\nfois.\n"
        }
      ],
      "explanation": "Autres exemples typiques : sac à dos,\nalignement de séquences (plus longue\nsous-séquence commune, distance\nd'édition). Tous ont en commun la\nsous-structure optimale et les\nsous-problèmes qui se chevauchent."
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "fibonacci",
        "code"
      ],
      "title": "Mémoïsation manuelle de Fibonacci",
      "partie": "exemples",
      "statement": "On écrit la fonction suivante :\n\n```python\nmemo = {}\ndef fib(n):\n    if n in memo:\n        return memo[n]\n    if n <= 1:\n        return n\n    memo[n] = fib(n - 1) + fib(n - 2)\n    return memo[n]\n```\n\nCombien d'appels effectifs (qui calculent\nréellement, sans utiliser le cache) sont\neffectués pour `fib(10)` ?",
      "options": [
        {
          "text": "Exactement un",
          "correct": false,
          "feedback": "Il faut bien calculer chaque valeur\nintermédiaire au moins une fois. La\nmémoïsation évite les recalculs, pas les\ncalculs initiaux.\n"
        },
        {
          "text": "Environ onze",
          "correct": true,
          "feedback": "Avec le cache, chaque `fib(n)` n'est calculée\nqu'une fois pour $n \\geq 2$ (les cas de base\n`fib(0)` et `fib(1)` ne sont pas mémoïsés),\nsoit une douzaine d'appels effectifs. Les\nappels sur une valeur déjà calculée sont\nsatisfaits immédiatement par `memo`, ce qui\névite l'explosion exponentielle.\n"
        },
        {
          "text": "Plus de mille",
          "correct": false,
          "feedback": "Cet ordre de grandeur correspond à la\nversion sans mémoïsation. Avec un cache,\nle nombre de calculs est bien plus\nmodeste.\n"
        },
        {
          "text": "Environ cent",
          "correct": false,
          "feedback": "C'est trop. Le nombre de valeurs distinctes\nà calculer est seulement $n + 1$, soit\n$11$, et non $n^2$.\n"
        }
      ],
      "explanation": "La mémoïsation transforme la complexité\n$O(2^n)$ en $O(n)$ : c'est un gain\nalgorithmique considérable. Le code reste\npourtant très proche de la version récursive\nnaïve."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "tabulation",
        "fibonacci"
      ],
      "title": "Tabulation pour Fibonacci",
      "partie": "techniques",
      "statement": "Quelle est la version **itérative ascendante** de\nla suite de Fibonacci ?",
      "options": [
        {
          "text": "```python\ndef fib(n):\n    return round((1.618 ** n) / 2.236)\n```\n",
          "correct": false,
          "feedback": "Cette formule est celle de Binet, qui\nutilise le nombre d'or. Elle ne relève pas\nde la programmation dynamique et devient\nimprécise pour de grandes valeurs de $n$ à\ncause des arrondis sur les flottants.\n"
        },
        {
          "text": "```python\ndef fib(n):\n    t = [0, 1]\n    for i in range(2, n + 1):\n        t.append(t[i-1] + t[i-2])\n    return t[n]\n```\n",
          "correct": true,
          "feedback": "On remplit un tableau `t` de taille $n + 1$\nen partant des cas de base ($t[0] = 0$ et\n$t[1] = 1$), puis on construit les valeurs\nsuivantes par récurrence. La complexité\nest $O(n)$ en temps comme en espace.\n"
        },
        {
          "text": "```python\ndef fib(n):\n    if n <= 1: return n\n    return fib(n-1) + fib(n-2)\n```\n",
          "correct": false,
          "feedback": "Il s'agit de la version récursive **naïve**,\nsans mémoïsation ni tabulation. Sa\ncomplexité est $O(2^n)$.\n"
        },
        {
          "text": "```python\ndef fib(n, memo={}):\n    if n in memo: return memo[n]\n    if n <= 1: return n\n    memo[n] = fib(n-1, memo) + fib(n-2, memo)\n    return memo[n]\n```\n",
          "correct": false,
          "feedback": "Il s'agit bien de programmation dynamique,\nmais **descendante** : la fonction reste\nrécursive, part de $n$ et mémoïse les\nrésultats en redescendant vers les cas de\nbase. La version ascendante remplit un\ntableau des cas de base vers $n$, sans\naucune récursion.\n"
        }
      ],
      "explanation": "L'approche itérative ascendante évite la récursion et le\ncache. Pour Fibonacci, on peut même optimiser\nl'espace en ne gardant que les deux dernières\nvaleurs : `a, b = b, a + b`."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "sac-a-dos"
      ],
      "title": "Problème du sac à dos",
      "partie": "exemples",
      "statement": "Le problème du **sac à dos $0/1$** consiste à\nchoisir un sous-ensemble d'objets à mettre dans\nun sac de capacité fixée pour maximiser leur\nvaleur totale. Quelle est sa complexité en\nprogrammation dynamique avec un sac de\ncapacité $W$ et $n$ objets ?",
      "options": [
        {
          "text": "$O(n!)$",
          "correct": false,
          "feedback": "Aucune raison particulière n'amène à une\ncomplexité factorielle pour ce problème.\n"
        },
        {
          "text": "$O(n)$",
          "correct": false,
          "feedback": "Il faut considérer chaque objet pour chaque\ncapacité possible du sac. La complexité\ndépend nécessairement des deux paramètres.\n"
        },
        {
          "text": "$O(2^n)$",
          "correct": false,
          "feedback": "Cette complexité correspond à l'approche\npar recherche exhaustive (essayer tous les\nsous-ensembles d'objets). La programmation\ndynamique est bien plus rapide.\n"
        },
        {
          "text": "$O(n \\cdot W)$",
          "correct": true,
          "feedback": "On remplit un tableau à deux dimensions de\ntaille $n \\times W$. Pour chaque case, le\ncalcul est en $O(1)$. La complexité totale\nest donc $O(nW)$.\n"
        }
      ],
      "explanation": "Le tableau compte une case par objet et\npar capacité de $0$ à $W$ : son\nremplissage demande donc de l'ordre de\n$n \\times W$ opérations, bien moins que\nles $2^n$ sous-ensembles d'une recherche\nexhaustive tant que $W$ reste\nraisonnable."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "comparaison",
        "glouton"
      ],
      "title": "Sac à dos : comparer les deux approches",
      "partie": "principe",
      "statement": "Pour le problème du sac à dos $0/1$,\nl'algorithme glouton qui prend les objets par\nordre décroissant du ratio « valeur / poids »\ndonne-t-il toujours la solution optimale ?",
      "options": [
        {
          "text": "L'algorithme glouton est plus rapide mais moins précis",
          "correct": false,
          "feedback": "Il s'agit ici d'optimum exact, pas\nd'approximation. Soit l'algorithme garantit\nl'optimum, soit il ne le garantit pas. Il\nn'y a pas de notion de précision\nintermédiaire.\n"
        },
        {
          "text": "Non, le glouton peut manquer l'optimum en $0/1$",
          "correct": true,
          "feedback": "Pour le sac à dos $0/1$, l'algorithme\nglouton peut être trompé. Avec un sac de\ncapacité $50$ et les objets\n$\\{(60, 10), (100, 20), (120, 30)\\}$\n(valeur, poids), le glouton prend d'abord\n$(60, 10)$ puis $(100, 20)$ (ratios $6$ puis\n$5$), pour une valeur de $160$. Or l'optimum\nest $(100, 20) + (120, 30)$, de poids $50$ et\nde valeur $220$.\n"
        },
        {
          "text": "Oui, c'est l'algorithme correct pour ce problème",
          "correct": false,
          "feedback": "Cette approche gloutonne est optimale pour\nle **sac à dos fractionnaire** (où l'on\npeut couper un objet), mais pas pour la\nversion $0/1$, où chaque objet est pris\nentièrement ou pas du tout.\n"
        },
        {
          "text": "Cela dépend du langage de programmation utilisé",
          "correct": false,
          "feedback": "La propriété est purement mathématique. Elle\nne dépend pas du langage choisi pour\nl'implémentation.\n"
        }
      ],
      "explanation": "Le sac à dos $0/1$ ne possède **pas** la\npropriété de choix glouton. Il faut donc\nexplorer plusieurs possibilités, ce que fait la\nprogrammation dynamique en $O(nW)$, à comparer\naux $O(2^n)$ de la recherche exhaustive."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "bottom-up",
        "top-down"
      ],
      "title": "Approche descendante et approche ascendante",
      "partie": "techniques",
      "statement": "Quelle est la différence entre l'approche\n**descendante** et l'approche **ascendante** en\nprogrammation dynamique ?",
      "options": [
        {
          "text": "La descendante est récursive, l'ascendante itérative",
          "correct": true,
          "feedback": "L'approche descendante part du problème\nglobal et descend dans les sous-problèmes,\nen utilisant un cache. L'approche\nascendante part des cas de base et\nconstruit progressivement la table des\nrésultats jusqu'au problème global.\n"
        },
        {
          "text": "L'approche descendante concerne les graphes, l'approche ascendante concerne les listes",
          "correct": false,
          "feedback": "Les deux approches s'appliquent à toutes\nles structures de données. La distinction\nn'a rien à voir avec le type des éléments\nmanipulés.\n"
        },
        {
          "text": "L'approche descendante est toujours plus rapide",
          "correct": false,
          "feedback": "En pratique, l'approche ascendante est\nsouvent **plus rapide**, car elle évite le\nsurcoût des appels récursifs. La complexité\nasymptotique est cependant la même dans les\ndeux cas.\n"
        },
        {
          "text": "Aucune, ces termes sont synonymes",
          "correct": false,
          "feedback": "Ce sont deux approches distinctes, même si\nelles permettent toutes deux de résoudre\nles mêmes problèmes.\n"
        }
      ],
      "explanation": "L'approche descendante (mémoïsation) est plus\nnaturelle quand la définition du problème est\nrécursive. L'approche ascendante (tabulation)\nest en général plus économe en mémoire et plus\nrapide en pratique. Le choix dépend du\ncontexte."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "tabulation",
        "cout-memoire"
      ],
      "title": "Deux variables au lieu d'un tableau",
      "partie": "techniques",
      "statement": "Voici deux calculs ascendants du terme de rang $n$ de la suite de\nFibonacci :\n\n```python\ndef fib_tableau(n):\n    f = [0 for k in range(n + 1)]\n    if n > 0:\n        f[1] = 1\n    for k in range(2, n + 1):\n        f[k] = f[k - 1] + f[k - 2]\n    return f[n]\n\ndef fib_deux(n):\n    a = 0\n    b = 1\n    for k in range(n):\n        c = a + b\n        a = b\n        b = c\n    return a\n```\n\nPour un grand $n$, que peut-on dire de la mémoire utilisée par ces\ndeux fonctions ?",
      "options": [
        {
          "text": "Toutes deux utilisent une mémoire proportionnelle à $n$, chacune faisant $n$ tours",
          "correct": false,
          "feedback": "Tu confonds temps et mémoire : les deux fonctions font bien de\nl'ordre de $n$ tours de boucle, mais `fib_deux` réutilise à chaque\ntour les mêmes trois variables au lieu de remplir un tableau.\n"
        },
        {
          "text": "Toutes deux utilisent une mémoire constante, aucune n'étant récursive",
          "correct": false,
          "feedback": "Tu ne regardes que la pile d'appels : `fib_tableau` n'est pas\nrécursive, mais elle construit une liste de $n + 1$ cases, ce qui\noccupe une mémoire proportionnelle à $n$.\n"
        },
        {
          "text": "Proportionnelle à $n$ pour `fib_tableau`, constante pour `fib_deux`",
          "correct": true,
          "feedback": "Exactement : chaque terme ne dépend que des deux termes qui le\ndevancent. `fib_deux` ne garde que ces deux valeurs et un intermédiaire,\nalors que `fib_tableau` conserve tous les termes calculés.\n"
        },
        {
          "text": "`fib_deux` utilise plus de mémoire, avec trois variables contre une",
          "correct": false,
          "feedback": "Tu comptes les noms au lieu des valeurs stockées : la seule\nvariable `f` de `fib_tableau` désigne une liste de $n + 1$\nnombres, bien plus que les trois nombres de `fib_deux`.\n"
        }
      ],
      "explanation": "Une programmation dynamique ascendante n'a pas toujours besoin de\ngarder tout son tableau : quand chaque valeur ne dépend que d'un\npetit nombre de valeurs calculées juste avant, on peut ne conserver que\ncelles-ci. Le temps reste le même, la mémoire devient constante."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "monnaie"
      ],
      "title": "Rendu de monnaie en programmation dynamique",
      "partie": "exemples",
      "statement": "Pour le **rendu de monnaie** avec un système de\npièces arbitraire, la programmation dynamique\ncalcule, en $O(n \\cdot s)$ (où $s$ est la somme\nà rendre et $n$ le nombre de pièces) :",
      "options": [
        {
          "text": "La somme totale rendue",
          "correct": false,
          "feedback": "La somme à rendre est donnée en entrée du\nproblème, elle n'est pas calculée par\nl'algorithme.\n"
        },
        {
          "text": "Le nombre minimal de pièces pour la somme",
          "correct": true,
          "feedback": "Pour chaque sous-somme $k \\in [0, s]$,\nl'algorithme calcule le nombre minimal de\npièces nécessaires. La récurrence utilisée\nest\n$\\text{ND}(k) = 1 + \\min_{p}\\, \\text{ND}(k - p)$,\noù $p$ parcourt les pièces disponibles.\n"
        },
        {
          "text": "La pièce la plus grande utilisée",
          "correct": false,
          "feedback": "Cette description correspond plutôt à\nl'approche gloutonne, qui choisit toujours\nla plus grande pièce disponible.\n"
        },
        {
          "text": "Une séquence ordonnée de pièces",
          "correct": false,
          "feedback": "La programmation dynamique calcule un\nnombre minimal, et non une séquence\nordonnée. La séquence des pièces\neffectivement utilisées peut être\nreconstruite ensuite, par un parcours à\nrebours du tableau.\n"
        }
      ],
      "explanation": "Le rendu de monnaie est l'exemple emblématique\nd'un problème où l'algorithme glouton **échoue**\nsur certains systèmes monétaires (par exemple\n$\\{1, 3, 4\\}$), alors que la programmation\ndynamique garantit l'optimum."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "comparaison-dpr"
      ],
      "title": "Programmation dynamique et diviser pour régner",
      "partie": "principe",
      "statement": "En quoi la programmation dynamique se\ndistingue-t-elle du paradigme « diviser pour\nrégner » classique ?",
      "options": [
        {
          "text": "Le paradigme « diviser pour régner » utilise toujours la récursivité, contrairement à la programmation dynamique",
          "correct": false,
          "feedback": "Les deux paradigmes peuvent utiliser la\nrécursivité. La programmation dynamique\npeut aussi être itérative (par tabulation),\nmais ce n'est pas la différence\nessentielle.\n"
        },
        {
          "text": "La programmation dynamique ne s'applique qu'aux problèmes portant sur des entiers",
          "correct": false,
          "feedback": "La programmation dynamique s'applique à\ntout problème possédant les deux propriétés\nrequises (sous-structure optimale et\nsous-problèmes qui se chevauchent), quel que soit\nle type des données manipulées.\n"
        },
        {
          "text": "La programmation dynamique est plus simple à coder",
          "correct": false,
          "feedback": "C'est subjectif et souvent l'inverse. Les\nalgorithmes de type « diviser pour régner »\nsont en général plus simples à concevoir.\n"
        },
        {
          "text": "En PD les sous-problèmes se recouvrent, pas en diviser-pour-régner",
          "correct": true,
          "feedback": "C'est la distinction fondamentale. Sans\nrecouvrement, la mémoïsation n'apporte\nrien. Avec recouvrement, elle apporte des\ngains majeurs.\n"
        }
      ],
      "explanation": "Pour identifier si un problème relève de la\nprogrammation dynamique, on peut tracer\nl'arbre des appels récursifs et chercher les\nnœuds dupliqués. S'il y en a beaucoup, la\nprogrammation dynamique est probablement le\nbon outil."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "memoire-temps"
      ],
      "title": "Compromis mémoire et temps",
      "partie": "techniques",
      "statement": "La programmation dynamique illustre un\ncompromis classique en algorithmique. Lequel ?",
      "options": [
        {
          "text": "Moins de mémoire consommée et moins de temps de calcul",
          "correct": false,
          "feedback": "Il n'y a pas de gain gratuit. Ne stocker\naucune valeur intermédiaire reviendrait au\ncas naïf récursif, qui est précisément\nbeaucoup plus lent.\n"
        },
        {
          "text": "Plus de mémoire, beaucoup moins de temps",
          "correct": true,
          "feedback": "On stocke les solutions intermédiaires\n(mémoire en plus) pour ne plus avoir à les\nrecalculer (temps en moins). C'est un\ncompromis central en algorithmique.\n"
        },
        {
          "text": "Aucun changement par rapport à l'algorithme naïf",
          "correct": false,
          "feedback": "La programmation dynamique apporte un gain\nmajeur sur de nombreux problèmes (suite de\nFibonacci, sac à dos, plus longue\nsous-séquence commune, etc.).\n"
        },
        {
          "text": "Plus de temps de calcul mais moins de mémoire consommée",
          "correct": false,
          "feedback": "C'est exactement l'inverse. La\nprogrammation dynamique consomme plus de\nmémoire, mais elle accélère\nconsidérablement le calcul.\n"
        }
      ],
      "explanation": "Pour la suite de Fibonacci, la version naïve a\nune complexité en $O(2^n)$ en temps avec\n$O(n)$ d'espace de pile. La version mémoïsée\npasse à $O(n)$ en temps avec $O(n)$ de mémoire :\non a légèrement augmenté l'espace, mais on a\ndivisé le temps par un facteur exponentiel."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "code",
        "lecture"
      ],
      "title": "Lecture d'une programmation dynamique ascendante",
      "partie": "techniques",
      "statement": "Que calcule la fonction Python suivante ?\n\n```python\ndef f(n):\n    T = [0] * (n + 1)\n    T[0] = 1\n    for i in range(1, n + 1):\n        T[i] = T[i-1] * i\n    return T[n]\n```",
      "options": [
        {
          "text": "La factorielle $n!$",
          "correct": true,
          "feedback": "On a $T[i] = T[i-1] \\cdot i = i!$, en\npartant de $T[0] = 1 = 0!$. La fonction\nrenvoie donc bien $n!$, par construction\nascendante.\n"
        },
        {
          "text": "La $n$-ième valeur de la suite de Fibonacci",
          "correct": false,
          "feedback": "La suite de Fibonacci utiliserait\nl'instruction `T[i] = T[i-1] + T[i-2]`,\nfondée sur une addition de deux termes\nprécédents, et non sur la multiplication\npar $i$.\n"
        },
        {
          "text": "$2^n$",
          "correct": false,
          "feedback": "Pour obtenir $2^n$, il faudrait écrire\n`T[i] = T[i-1] * 2` plutôt que\n`T[i-1] * i`.\n"
        },
        {
          "text": "La somme $1 + 2 + \\cdots + n$",
          "correct": false,
          "feedback": "Une somme s'obtiendrait par addition. Or\nla fonction effectue une multiplication\n(`T[i-1] * i`), qui correspond à un\nproduit cumulé.\n"
        }
      ],
      "explanation": "Cet exemple illustre une **programmation dynamique ascendante** très simple : on\nremplit un tableau en partant du cas de base.\nC'est l'archétype de la tabulation pour une\nrécurrence simple."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "tabulation",
        "trace"
      ],
      "title": "Découper une barre au meilleur prix",
      "partie": "techniques",
      "statement": "Un menuisier vend des morceaux de barre : `prix[m]` est le prix,\nen euros, d'un morceau de longueur `m`. La fonction suivante\ncalcule le meilleur prix d'une barre de longueur `n` que l'on peut\ncouper en morceaux :\n\n```python\ndef meilleur_prix(n, prix):\n    t = [0 for k in range(n + 1)]\n    for k in range(1, n + 1):\n        meilleur = 0\n        for m in range(1, k + 1):\n            if prix[m] + t[k - m] > meilleur:\n                meilleur = prix[m] + t[k - m]\n        t[k] = meilleur\n    return t[n]\n```\n\nQue renvoie `meilleur_prix(4, [0, 1, 5, 8, 9])` ?",
      "options": [
        {
          "text": "$4$ euros",
          "correct": false,
          "feedback": "Erreur : $4$ euros correspond à quatre morceaux de longueur $1$.\nLa boucle interne essaie aussi des premiers morceaux de longueur\n$2$, $3$ ou $4$, bien plus rentables.\n"
        },
        {
          "text": "$8$ euros",
          "correct": false,
          "feedback": "Erreur : $8$ est la valeur de `t[3]`, le meilleur prix d'une\nbarre de longueur $3$. La fonction renvoie `t[4]`, calculé au\ndernier tour de la boucle externe.\n"
        },
        {
          "text": "$9$ euros",
          "correct": false,
          "feedback": "Erreur : $9$ euros, c'est la barre vendue d'un seul tenant, ou\ncoupée en $3 + 1$. Pour `k = 4`, la fonction essaie aussi\n`prix[2] + t[2]`, qui vaut $5 + 5 = 10$.\n"
        },
        {
          "text": "$10$ euros",
          "correct": true,
          "feedback": "Bonne réponse : le tableau se remplit en `[0, 1, 5, 8, 10]`.\nPour `k = 4`, les candidats valent $1 + 8$, $5 + 5$, $8 + 1$ et\n$9 + 0$ : le meilleur est $10$, avec deux morceaux de longueur\n$2$.\n"
        }
      ],
      "explanation": "La case `t[k]` contient le meilleur prix d'une barre de longueur\n`k`. On la calcule en essayant chaque longueur `m` du premier\nmorceau, le reste valant `t[k - m]`, déjà calculé. Chaque\nsous-problème n'est résolu qu'une fois."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "reconstruction-solution"
      ],
      "title": "Reconstruire la solution",
      "partie": "techniques",
      "statement": "En programmation dynamique, on calcule souvent\nune **valeur optimale** (par exemple le coût\nminimal). Comment retrouve-t-on la **solution effective**, c'est-à-dire la séquence de\ndécisions qui mène à cet optimum ?",
      "options": [
        {
          "text": "On ne peut pas, la programmation dynamique ne donne que la valeur optimale",
          "correct": false,
          "feedback": "La solution effective peut tout à fait\nêtre reconstruite : c'est ce qui rend la\nprogrammation dynamique utile dans les\napplications réelles.\n"
        },
        {
          "text": "On lance plusieurs programmations dynamiques avec différents paramètres pour identifier les choix",
          "correct": false,
          "feedback": "Cette approche serait inefficace. Une seule\nexécution suffit, à condition de bien lire\nla table à rebours.\n"
        },
        {
          "text": "On la reconstruit en parcourant la table à rebours, à partir de la valeur finale",
          "correct": true,
          "feedback": "On remonte dans la table en suivant les\nchoix qui ont mené à chaque valeur\noptimale. Cela permet de retrouver la\nséquence de décisions sans alourdir le\ncalcul lui-même.\n"
        },
        {
          "text": "On stocke aussi la solution dans la table",
          "correct": false,
          "feedback": "C'est possible mais coûteux en mémoire, et\nsouvent inutile : la solution se\nreconstruit toujours en $O(n)$ après le\ncalcul, par un parcours à rebours.\n"
        }
      ],
      "explanation": "Cette technique de reconstruction à rebours\nest universelle en programmation dynamique :\naprès avoir rempli la table, on retrace les\nchoix faits à chaque étape pour reconstruire\nla solution complète."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese",
        "identification"
      ],
      "title": "Reconnaître un problème de programmation dynamique",
      "partie": "principe",
      "statement": "Pour qu'un problème se résolve efficacement par\nprogrammation dynamique, il doit posséder :",
      "options": [
        {
          "text": "Une décomposition logarithmique du problème",
          "correct": false,
          "feedback": "Cette caractéristique correspond plutôt au\nparadigme « diviser pour régner »\nclassique. Ce n'est pas un prérequis de la\nprogrammation dynamique.\n"
        },
        {
          "text": "Un seul cas de base et une seule récurrence",
          "correct": false,
          "feedback": "Le nombre de cas de base ou de récurrences\nn'est pas un critère. Ce sont les\npropriétés structurelles du problème qui\ncomptent.\n"
        },
        {
          "text": "Une fonction objectif uniquement de minimisation",
          "correct": false,
          "feedback": "La programmation dynamique s'applique aussi\nà des problèmes de maximisation (sac à dos,\nplus longue sous-séquence) ou de comptage\n(nombre de chemins).\n"
        },
        {
          "text": "Sous-structure optimale et sous-problèmes répétés",
          "correct": true,
          "feedback": "Ces deux propriétés sont nécessaires. La\nsous-structure optimale permet de combiner\nles solutions, et les sous-problèmes qui\nse chevauchent rendent la mémoïsation\nutile.\n"
        }
      ],
      "explanation": "Ces deux propriétés sont l'identité de la\nprogrammation dynamique. Sans la sous-structure\noptimale, il est impossible de combiner les\nsolutions. Sans le recouvrement des\nsous-problèmes, la mémoïsation est inutile, et\nl'on retombe sur le paradigme « diviser pour\nrégner » classique."
    },
    {
      "id": "q26",
      "difficulty": 3,
      "skills": [
        "chemins-grille"
      ],
      "title": "Chemins dans une grille",
      "partie": "exemples",
      "statement": "On veut compter le nombre de chemins\nmonotones (n'allant qu'à droite ou en bas)\nd'un coin d'une grille rectangulaire de $m$\nlignes et $n$ colonnes au coin opposé. Quelle\nest la récurrence appropriée et la complexité\npar programmation dynamique ?",
      "options": [
        {
          "text": "$C(i, j) = C(i-1, j) + C(i, j-1)$,\ncomplexité $O(m \\cdot n)$\n",
          "correct": true,
          "feedback": "Bonne réponse : pour atteindre la case\n$(i, j)$, on vient soit du dessus\n$(i-1, j)$ soit de la gauche $(i, j-1)$.\nLes cas de base sont les bords, où il\nn'existe qu'un seul chemin (descente ou\ndéplacement à droite uniquement), d'où\n$C(0, j) = C(i, 0) = 1$. On\nremplit un tableau à deux dimensions de\ntaille $m \\times n$, chaque case en\n$O(1)$, donc complexité totale\n$O(m \\cdot n)$.\n"
        },
        {
          "text": "$C(i, j) = C(i-1, j-1) + 1$, complexité\n$O(m \\cdot n)$\n",
          "correct": false,
          "feedback": "Erreur : la récurrence proposée ne\ncorrespond pas au problème. On ne peut se\ndéplacer qu'à droite **ou** vers le bas,\npas en diagonale. La récurrence correcte\nadditionne les chemins venant de la case\ndu dessus et de la case de gauche.\n"
        },
        {
          "text": "$C(i, j) = C(i-1, j) \\cdot C(i, j-1)$,\ncomplexité $O(m + n)$\n",
          "correct": false,
          "feedback": "Erreur : pour compter le nombre de\nchemins, on **additionne** les\npossibilités, on ne les multiplie pas. La\ncomplexité est aussi sous-estimée :\nremplir un tableau à deux dimensions\ncoûte $O(m \\cdot n)$, pas $O(m + n)$.\n"
        },
        {
          "text": "$C(i, j) = i + j$, calcul direct en\n$O(1)$ sans tableau\n",
          "correct": false,
          "feedback": "Erreur : le nombre de chemins n'est pas la\nsomme des coordonnées mais bien le\ncoefficient binomial $\\binom{i+j}{i}$. Une\nformule fermée existe, mais elle se\ncalcule en $O(\\min(i, j))$ multiplications,\npas en temps constant.\n"
        }
      ],
      "explanation": "Ce problème est l'un des plus simples\nillustrant la programmation dynamique sur une\ntable à deux dimensions. La même technique\ns'étend aux variantes (cases bloquées,\nsommes minimales, etc.)."
    },
    {
      "id": "q28",
      "difficulty": 2,
      "skills": [
        "rendu-monnaie",
        "glouton",
        "comparaison"
      ],
      "title": "Trace sur le rendu de monnaie",
      "partie": "exemples",
      "statement": "Avec les pièces $\\{1, 3, 4\\}$ et la somme à\nrendre $s = 6$, quel est le **nombre minimal**\nde pièces calculé par programmation dynamique\n(et l'écart avec la méthode gloutonne qui\nprend toujours la plus grosse pièce\npossible) ?",
      "options": [
        {
          "text": "La programmation dynamique trouve $1$\npièce(s), l'algorithme glouton en utilise $2$\n",
          "correct": false,
          "feedback": "Erreur : aucune pièce de valeur $6$ n'existe dans le système monétaire $\\{1, 3, 4\\}$, donc une pièce ne peut pas suffire. Le minimum atteignable est de\n$2$ pièces (combinaison $3 + 3$).\n"
        },
        {
          "text": "La programmation dynamique et l'algorithme\nglouton trouvent tous deux $3$ pièces\n",
          "correct": false,
          "feedback": "Erreur : si c'était le cas, la\nprogrammation dynamique n'aurait aucun\nintérêt sur ce problème. Refaire le\ncalcul : avec $3 + 3$, $2$ pièces\nsuffisent, ce que l'algorithme glouton\nrate.\n"
        },
        {
          "text": "Aucune des deux méthodes n'aboutit, car\naucune combinaison de ces pièces ne donne $6$\n",
          "correct": false,
          "feedback": "Erreur : la combinaison $3 + 3 = 6$ est\nparfaitement valide. De même\n$1 + 1 + 4 = 6$ ou $1 + 1 + 1 + 3 = 6$\nfonctionnent. Le problème est résoluble.\n"
        },
        {
          "text": "La programmation dynamique trouve $2$\npièces, l'algorithme glouton en utilise $3$\n",
          "correct": true,
          "feedback": "Bonne réponse : c'est l'exemple\npédagogique classique où la stratégie\ngloutonne échoue à atteindre l'optimum.\nGlouton : prendre $4$, reste $2$, prendre $1$, prendre $1$, total $3$ pièces\n(combinaison $4 + 1 + 1$).\nProgrammation dynamique : explore toutes\nles combinaisons et trouve $3 + 3 = 6$,\nsoit seulement $2$ pièces. Cas\nparadigmatique de l'intérêt de la\nprogrammation dynamique.\n"
        }
      ],
      "explanation": "Cet exemple est emblématique : il prouve\nqu'avec un système monétaire arbitraire,\nl'algorithme glouton n'est plus optimal. Avec\nles systèmes monétaires usuels (euros,\ndollars), l'algorithme glouton fonctionne\ngrâce à des propriétés structurelles\nparticulières des dénominations choisies.",
      "variants": [
        {
          "statement": "Avec les pièces $\\{1, 3, 4\\}$ et la somme à\nrendre $s = 6$, quel est le **nombre minimal**\nde pièces calculé par programmation dynamique\n(et l'écart avec la méthode gloutonne qui\nprend toujours la plus grosse pièce\npossible) ?",
          "options": [
            {
              "text": "La programmation dynamique trouve $1$\npièce(s), l'algorithme glouton en utilise $2$\n",
              "correct": false,
              "feedback": "Erreur : aucune pièce de valeur $6$ n'existe dans le système monétaire $\\{1, 3, 4\\}$, donc une pièce ne peut pas suffire. Le minimum atteignable est de\n$2$ pièces (combinaison $3 + 3$).\n"
            },
            {
              "text": "La programmation dynamique et l'algorithme\nglouton trouvent tous deux $3$ pièces\n",
              "correct": false,
              "feedback": "Erreur : si c'était le cas, la\nprogrammation dynamique n'aurait aucun\nintérêt sur ce problème. Refaire le\ncalcul : avec $3 + 3$, $2$ pièces\nsuffisent, ce que l'algorithme glouton\nrate.\n"
            },
            {
              "text": "Aucune des deux méthodes n'aboutit, car\naucune combinaison de ces pièces ne donne $6$\n",
              "correct": false,
              "feedback": "Erreur : la combinaison $3 + 3 = 6$ est\nparfaitement valide. De même\n$1 + 1 + 4 = 6$ ou $1 + 1 + 1 + 3 = 6$\nfonctionnent. Le problème est résoluble.\n"
            },
            {
              "text": "La programmation dynamique trouve $2$\npièces, l'algorithme glouton en utilise $3$\n",
              "correct": true,
              "feedback": "Bonne réponse : c'est l'exemple\npédagogique classique où la stratégie\ngloutonne échoue à atteindre l'optimum.\nGlouton : prendre $4$, reste $2$, prendre $1$, prendre $1$, total $3$ pièces\n(combinaison $4 + 1 + 1$).\nProgrammation dynamique : explore toutes\nles combinaisons et trouve $3 + 3 = 6$,\nsoit seulement $2$ pièces. Cas\nparadigmatique de l'intérêt de la\nprogrammation dynamique.\n"
            }
          ],
          "explanation": "Cet exemple est emblématique : il prouve\nqu'avec un système monétaire arbitraire,\nl'algorithme glouton n'est plus optimal. Avec\nles systèmes monétaires usuels (euros,\ndollars), l'algorithme glouton fonctionne\ngrâce à des propriétés structurelles\nparticulières des dénominations choisies."
        },
        {
          "statement": "Avec les pièces $\\{1, 5, 7\\}$ et la somme à\nrendre $s = 10$, quel est le **nombre minimal**\nde pièces calculé par programmation dynamique\n(et l'écart avec la méthode gloutonne qui\nprend toujours la plus grosse pièce\npossible) ?",
          "options": [
            {
              "text": "La programmation dynamique trouve $1$\npièce(s), l'algorithme glouton en utilise $2$\n",
              "correct": false,
              "feedback": "Erreur : aucune pièce de valeur $10$ n'existe dans le système monétaire $\\{1, 5, 7\\}$, donc une pièce ne peut pas suffire. Le minimum atteignable est de\n$2$ pièces (combinaison $5 + 5$).\n"
            },
            {
              "text": "La programmation dynamique et l'algorithme\nglouton trouvent tous deux $4$ pièces\n",
              "correct": false,
              "feedback": "Erreur : si c'était le cas, la\nprogrammation dynamique n'aurait aucun\nintérêt sur ce problème. Refaire le\ncalcul : avec $5 + 5$, $2$ pièces\nsuffisent, ce que l'algorithme glouton\nrate.\n"
            },
            {
              "text": "Aucune des deux méthodes n'aboutit, car\naucune combinaison de ces pièces ne donne $10$\n",
              "correct": false,
              "feedback": "Erreur : la combinaison $5 + 5 = 10$ est\nparfaitement valide. De même\n$7 + 1 + 1 + 1 = 10$ ou $5 + 1 + 1 + 1 + 1 + 1 = 10$\nfonctionnent. Le problème est résoluble.\n"
            },
            {
              "text": "La programmation dynamique trouve $2$\npièces, l'algorithme glouton en utilise $4$\n",
              "correct": true,
              "feedback": "Bonne réponse : c'est l'exemple\npédagogique classique où la stratégie\ngloutonne échoue à atteindre l'optimum.\nGlouton : prendre $7$, reste $3$, prendre $1$ trois fois, total $4$ pièces\n(combinaison $7 + 1 + 1 + 1$).\nProgrammation dynamique : explore toutes\nles combinaisons et trouve $5 + 5 = 10$,\nsoit seulement $2$ pièces. Cas\nparadigmatique de l'intérêt de la\nprogrammation dynamique.\n"
            }
          ],
          "explanation": "Cet exemple est emblématique : il prouve\nqu'avec un système monétaire arbitraire,\nl'algorithme glouton n'est plus optimal. Avec\nles systèmes monétaires usuels (euros,\ndollars), l'algorithme glouton fonctionne\ngrâce à des propriétés structurelles\nparticulières des dénominations choisies."
        },
        {
          "statement": "Avec les pièces $\\{1, 3, 4\\}$ et la somme à\nrendre $s = 10$, quel est le **nombre minimal**\nde pièces calculé par programmation dynamique\n(et l'écart avec la méthode gloutonne qui\nprend toujours la plus grosse pièce\npossible) ?",
          "options": [
            {
              "text": "La programmation dynamique trouve $2$\npièce(s), l'algorithme glouton en utilise $3$\n",
              "correct": false,
              "feedback": "Erreur : deux pièces de $\\{1, 3, 4\\}$ totalisent au plus $8$, donc deux pièces ne peuvent pas suffire pour $10$. Le minimum atteignable est de\n$3$ pièces (combinaison $4 + 3 + 3$).\n"
            },
            {
              "text": "La programmation dynamique et l'algorithme\nglouton trouvent tous deux $4$ pièces\n",
              "correct": false,
              "feedback": "Erreur : si c'était le cas, la\nprogrammation dynamique n'aurait aucun\nintérêt sur ce problème. Refaire le\ncalcul : avec $4 + 3 + 3$, $3$ pièces\nsuffisent, ce que l'algorithme glouton\nrate.\n"
            },
            {
              "text": "Aucune des deux méthodes n'aboutit, car\naucune combinaison de ces pièces ne donne $10$\n",
              "correct": false,
              "feedback": "Erreur : la combinaison $4 + 3 + 3 = 10$ est\nparfaitement valide. De même\n$4 + 4 + 1 + 1 = 10$ ou $3 + 3 + 3 + 1 = 10$\nfonctionnent. Le problème est résoluble.\n"
            },
            {
              "text": "La programmation dynamique trouve $3$\npièces, l'algorithme glouton en utilise $4$\n",
              "correct": true,
              "feedback": "Bonne réponse : c'est l'exemple\npédagogique classique où la stratégie\ngloutonne échoue à atteindre l'optimum.\nGlouton : prendre $4$, reste $6$, prendre $4$, reste $2$, prendre $1$, prendre $1$, total $4$ pièces\n(combinaison $4 + 4 + 1 + 1$).\nProgrammation dynamique : explore toutes\nles combinaisons et trouve $4 + 3 + 3 = 10$,\nsoit seulement $3$ pièces. Cas\nparadigmatique de l'intérêt de la\nprogrammation dynamique.\n"
            }
          ],
          "explanation": "Cet exemple est emblématique : il prouve\nqu'avec un système monétaire arbitraire,\nl'algorithme glouton n'est plus optimal. Avec\nles systèmes monétaires usuels (euros,\ndollars), l'algorithme glouton fonctionne\ngrâce à des propriétés structurelles\nparticulières des dénominations choisies."
        }
      ]
    },
    {
      "id": "q29",
      "difficulty": 3,
      "skills": [
        "sac-a-dos",
        "tabulation"
      ],
      "title": "Trace du sac à dos",
      "partie": "exemples",
      "statement": "On dispose d'un sac de capacité $W = 5$ et de\ntrois objets : $O_1$ (poids $2$, valeur $3$),\n$O_2$ (poids $3$, valeur $4$), $O_3$ (poids\n$4$, valeur $5$). Quelle est la valeur\noptimale obtenue par programmation\ndynamique ?",
      "options": [
        {
          "text": "$12$ (en prenant les trois objets)\n",
          "correct": false,
          "feedback": "Erreur : prendre les trois objets pèse\n$2 + 3 + 4 = 9 \\mathrm{kg}$, bien\nau-delà de la capacité du sac. C'est la\ndifficulté du sac à dos : on doit\nrenoncer à des objets précieux à cause\nde la contrainte de poids.\n"
        },
        {
          "text": "$7$ (en prenant $O_1$ et $O_2$)\n",
          "correct": true,
          "feedback": "Bonne réponse : on construit la table\n$V[i][w]$ donnant la valeur optimale en\nconsidérant les $i$ premiers objets pour\nune capacité $w$. Quelques valeurs clés :\n$V[2][5] = \\max(V[1][5], V[1][2] + 4) = \\max(3, 3 + 4) = 7$. À l'arrivée $V[3][5] = \\max(V[2][5], V[2][1] + 5) = \\max(7, 0 + 5) = 7$. La solution optimale prend $O_1$ ($2 \\mathrm{kg}$, $3$) et $O_2$ ($3 \\mathrm{kg}$, $4$), poids total $5$, valeur totale $7$.\n"
        },
        {
          "text": "$5$ (en prenant uniquement $O_3$)\n",
          "correct": false,
          "feedback": "Erreur : prendre seulement $O_3$ ne donne\nque $5$ de valeur, alors que la combinaison $O_1 + O_2$ donne $7$ pour un poids identique au sac (capacité atteinte exactement). Il vaut donc mieux\ncombiner plusieurs objets.\n"
        },
        {
          "text": "$9$ (en prenant $O_2$ et $O_3$)\n",
          "correct": false,
          "feedback": "Erreur : la combinaison $O_2$ + $O_3$ pèse $3 + 4 = 7 \\mathrm{kg}$, ce qui dépasse la capacité $W = 5$ du sac. Cette solution n'est donc pas réalisable.\n"
        }
      ],
      "explanation": "Construire le tableau $V$ à la main est un\nexcellent exercice. Il révèle que la\nprogrammation dynamique « teste » toutes les\ncombinaisons utiles sans en énumérer\nexplicitement aucune. Pour reconstruire la\nsolution (les objets choisis), on remonte\ndans le tableau à rebours en regardant\npour chaque case quelle décision a mené à\nl'optimum.",
      "variants": [
        {
          "statement": "On dispose d'un sac de capacité $W = 5$ et de\ntrois objets : $O_1$ (poids $2$, valeur $3$),\n$O_2$ (poids $3$, valeur $4$), $O_3$ (poids\n$4$, valeur $5$). Quelle est la valeur\noptimale obtenue par programmation\ndynamique ?",
          "options": [
            {
              "text": "$12$ (en prenant les trois objets)\n",
              "correct": false,
              "feedback": "Erreur : prendre les trois objets pèse\n$2 + 3 + 4 = 9 \\mathrm{kg}$, bien\nau-delà de la capacité du sac. C'est la\ndifficulté du sac à dos : on doit\nrenoncer à des objets précieux à cause\nde la contrainte de poids.\n"
            },
            {
              "text": "$7$ (en prenant $O_1$ et $O_2$)\n",
              "correct": true,
              "feedback": "Bonne réponse : on construit la table\n$V[i][w]$ donnant la valeur optimale en\nconsidérant les $i$ premiers objets pour\nune capacité $w$. Quelques valeurs clés :\n$V[2][5] = \\max(V[1][5], V[1][2] + 4) = \\max(3, 3 + 4) = 7$. À l'arrivée $V[3][5] = \\max(V[2][5], V[2][1] + 5) = \\max(7, 0 + 5) = 7$. La solution optimale prend $O_1$ ($2 \\mathrm{kg}$, $3$) et $O_2$ ($3 \\mathrm{kg}$, $4$), poids total $5$, valeur totale $7$.\n"
            },
            {
              "text": "$5$ (en prenant uniquement $O_3$)\n",
              "correct": false,
              "feedback": "Erreur : prendre seulement $O_3$ ne donne\nque $5$ de valeur, alors que la combinaison $O_1 + O_2$ donne $7$ pour un poids identique au sac (capacité atteinte exactement). Il vaut donc mieux\ncombiner plusieurs objets.\n"
            },
            {
              "text": "$9$ (en prenant $O_2$ et $O_3$)\n",
              "correct": false,
              "feedback": "Erreur : la combinaison $O_2$ + $O_3$ pèse $3 + 4 = 7 \\mathrm{kg}$, ce qui dépasse la capacité $W = 5$ du sac. Cette solution n'est donc pas réalisable.\n"
            }
          ],
          "explanation": "Construire le tableau $V$ à la main est un\nexcellent exercice. Il révèle que la\nprogrammation dynamique « teste » toutes les\ncombinaisons utiles sans en énumérer\nexplicitement aucune. Pour reconstruire la\nsolution (les objets choisis), on remonte\ndans le tableau à rebours en regardant\npour chaque case quelle décision a mené à\nl'optimum."
        },
        {
          "statement": "On dispose d'un sac de capacité $W = 6$ et de\ntrois objets : $O_1$ (poids $2$, valeur $3$),\n$O_2$ (poids $3$, valeur $4$), $O_3$ (poids\n$4$, valeur $5$). Quelle est la valeur\noptimale obtenue par programmation\ndynamique ?",
          "options": [
            {
              "text": "$12$ (en prenant les trois objets)\n",
              "correct": false,
              "feedback": "Erreur : prendre les trois objets pèse\n$2 + 3 + 4 = 9 \\mathrm{kg}$, bien\nau-delà de la capacité du sac. C'est la\ndifficulté du sac à dos : on doit\nrenoncer à des objets précieux à cause\nde la contrainte de poids.\n"
            },
            {
              "text": "$8$ (en prenant $O_1$ et $O_3$)\n",
              "correct": true,
              "feedback": "Bonne réponse : on construit la table\n$V[i][w]$ donnant la valeur optimale en\nconsidérant les $i$ premiers objets pour\nune capacité $w$. Quelques valeurs clés :\n$V[2][6] = \\max(V[1][6], V[1][3] + 4) = \\max(3, 3 + 4) = 7$. À l'arrivée $V[3][6] = \\max(V[2][6], V[2][2] + 5) = \\max(7, 3 + 5) = 8$. La solution optimale prend $O_1$ ($2 \\mathrm{kg}$, $3$) et $O_3$ ($4 \\mathrm{kg}$, $5$), poids total $6$, valeur totale $8$.\n"
            },
            {
              "text": "$5$ (en prenant uniquement $O_3$)\n",
              "correct": false,
              "feedback": "Erreur : prendre seulement $O_3$ ne donne\nque $5$ de valeur, alors que la combinaison $O_1 + O_3$ donne $8$ pour un poids de $6 \\mathrm{kg}$, exactement la capacité. Il vaut donc mieux\ncombiner plusieurs objets.\n"
            },
            {
              "text": "$9$ (en prenant $O_2$ et $O_3$)\n",
              "correct": false,
              "feedback": "Erreur : la combinaison $O_2$ + $O_3$ pèse $3 + 4 = 7 \\mathrm{kg}$, ce qui dépasse la capacité $W = 6$ du sac. Cette solution n'est donc pas réalisable.\n"
            }
          ],
          "explanation": "Construire le tableau $V$ à la main est un\nexcellent exercice. Il révèle que la\nprogrammation dynamique « teste » toutes les\ncombinaisons utiles sans en énumérer\nexplicitement aucune. Pour reconstruire la\nsolution (les objets choisis), on remonte\ndans le tableau à rebours en regardant\npour chaque case quelle décision a mené à\nl'optimum."
        },
        {
          "statement": "On dispose d'un sac de capacité $W = 7$ et de\ntrois objets : $O_1$ (poids $2$, valeur $3$),\n$O_2$ (poids $3$, valeur $4$), $O_3$ (poids\n$4$, valeur $5$). Quelle est la valeur\noptimale obtenue par programmation\ndynamique ?",
          "options": [
            {
              "text": "$12$ (en prenant les trois objets)\n",
              "correct": false,
              "feedback": "Erreur : prendre les trois objets pèse\n$2 + 3 + 4 = 9 \\mathrm{kg}$, bien\nau-delà de la capacité du sac. C'est la\ndifficulté du sac à dos : on doit\nrenoncer à des objets précieux à cause\nde la contrainte de poids.\n"
            },
            {
              "text": "$9$ (en prenant $O_2$ et $O_3$)\n",
              "correct": true,
              "feedback": "Bonne réponse : on construit la table\n$V[i][w]$ donnant la valeur optimale en\nconsidérant les $i$ premiers objets pour\nune capacité $w$. Quelques valeurs clés :\n$V[2][7] = \\max(V[1][7], V[1][4] + 4) = \\max(3, 3 + 4) = 7$. À l'arrivée $V[3][7] = \\max(V[2][7], V[2][3] + 5) = \\max(7, 4 + 5) = 9$. La solution optimale prend $O_2$ ($3 \\mathrm{kg}$, $4$) et $O_3$ ($4 \\mathrm{kg}$, $5$), poids total $7$, valeur totale $9$.\n"
            },
            {
              "text": "$5$ (en prenant uniquement $O_3$)\n",
              "correct": false,
              "feedback": "Erreur : prendre seulement $O_3$ ne donne\nque $5$ de valeur, alors que la combinaison $O_2 + O_3$ donne $9$ pour un poids de $7 \\mathrm{kg}$, exactement la capacité. Il vaut donc mieux\ncombiner plusieurs objets.\n"
            },
            {
              "text": "$8$ (en prenant $O_1$ et $O_3$)\n",
              "correct": false,
              "feedback": "Erreur : la combinaison $O_1$ + $O_3$ est réalisable ($6 \\mathrm{kg}$) mais ne vaut que $8$, alors que $O_2 + O_3$ tient dans le sac ($7 \\mathrm{kg}$) et vaut $9$.\n"
            }
          ],
          "explanation": "Construire le tableau $V$ à la main est un\nexcellent exercice. Il révèle que la\nprogrammation dynamique « teste » toutes les\ncombinaisons utiles sans en énumérer\nexplicitement aucune. Pour reconstruire la\nsolution (les objets choisis), on remonte\ndans le tableau à rebours en regardant\npour chaque case quelle décision a mené à\nl'optimum."
        }
      ]
    },
    {
      "id": "q30",
      "difficulty": 3,
      "skills": [
        "code-sac-a-dos"
      ],
      "title": "Code du sac à dos en programmation dynamique",
      "partie": "exemples",
      "statement": "Quel code Python implémente correctement la\nprogrammation dynamique ascendante (par\ntabulation) pour le sac à dos $0/1$ avec $n$\nobjets de poids `poids[i]` et valeurs\n`valeurs[i]` et un sac de capacité `W` ?",
      "options": [
        {
          "text": "```python\ndef sac_a_dos(poids, valeurs, W):\n    total = 0\n    for i in range(len(poids)):\n        total += valeurs[i]\n    return total\n```\n",
          "correct": false,
          "feedback": "Erreur : ce code retourne la somme de\n**toutes** les valeurs sans tenir compte\nde la contrainte de poids. Pour trois\nobjets de poids $2$, $3$, $4$ et de\nvaleurs $3$, $4$, $5$ avec $W = 5$, il\nrenvoie $12$ alors que la valeur optimale\nvaut $7$.\n"
        },
        {
          "text": "```python\ndef sac_a_dos(poids, valeurs, W):\n    n = len(poids)\n    V = [[0] * (W + 1) for _ in range(n + 1)]\n    for i in range(1, n + 1):\n        for w in range(W + 1):\n            if poids[i-1] <= w:\n                V[i][w] = max(V[i-1][w],\n                              V[i-1][w-poids[i-1]] + valeurs[i-1])\n            else:\n                V[i][w] = V[i-1][w]\n    return V[n][W]\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : on construit un tableau à\ndeux dimensions de taille\n$(n+1) \\times (W+1)$. Pour chaque case\n$(i, w)$ : si l'objet $i$ rentre dans le\nsac (`poids[i-1] <= w`), on prend le\nmaximum entre « ne pas le prendre »\n(`V[i-1][w]`) et « le prendre »\n(`V[i-1][w-poids[i-1]] + valeurs[i-1]`) ;\nsinon, on ne peut pas le prendre. Indices\n$i-1$ car les listes Python commencent\nà $0$.\n"
        },
        {
          "text": "```python\ndef sac_a_dos(poids, valeurs, W):\n    # Glouton : meilleur ratio valeur/poids d'abord\n    couples = []\n    for i in range(len(poids)):\n        couples.append((valeurs[i] / poids[i], i))\n    total = 0\n    capa = W\n    for ratio, i in sorted(couples, reverse=True):\n        if poids[i] <= capa:\n            total += valeurs[i]\n            capa -= poids[i]\n    return total\n```\n",
          "correct": false,
          "feedback": "Erreur : c'est l'algorithme **glouton** par\nratio valeur/poids. Il est optimal pour\nle sac à dos **fractionnaire** (où l'on\npeut couper des objets), mais pas pour\nla version $0/1$. Pour des poids $1$,\n$2$, $3$, des valeurs $6$, $10$, $12$ et\n$W = 5$, il prend l'objet de ratio $6$\npuis celui de ratio $5$ et renvoie $16$,\nalors que les objets de poids $2$ et $3$\ndonnent $22$.\n"
        },
        {
          "text": "```python\ndef sac_a_dos(poids, valeurs, W):\n    n = len(poids)\n    V = [[0] * (W + 1) for _ in range(n + 1)]\n    for i in range(1, n + 1):\n        for w in range(W + 1):\n            if poids[i-1] <= w:\n                V[i][w] = max(V[i-1][w],\n                              V[i][w-poids[i-1]] + valeurs[i-1])\n            else:\n                V[i][w] = V[i-1][w]\n    return V[n][W]\n```\n",
          "correct": false,
          "feedback": "Erreur : dans le second terme du `max`,\nla case consultée est `V[i][w-poids[i-1]]`\n(ligne $i$ au lieu de $i-1$), ce qui\nautorise à reprendre l'objet $i$\nplusieurs fois. C'est le sac à dos\n**non borné**, pas la version $0/1$.\nAvec un seul objet de poids $2$ et de\nvaleur $3$ pour $W = 4$, ce code renvoie\n$6$ (l'objet pris deux fois) au lieu de\n$3$.\n"
        }
      ],
      "explanation": "Complexité : $O(n \\cdot W)$ en temps et en\nespace. Optimisation possible : ne conserver\nque deux lignes du tableau, celle que l'on\nremplit et celle du dessus, ce qui ramène\nl'espace à $O(W)$. Pour\nreconstruire la solution effective, on\nremonte le tableau de la case $V[n][W]$ vers\nla case $V[0][0]$, en notant à chaque\nétape si l'objet $i$ a été pris."
    },
    {
      "id": "q31",
      "difficulty": 3,
      "skills": [
        "memoisation-vs-tabulation"
      ],
      "title": "Mesurer le gain de la mémoïsation",
      "partie": "techniques",
      "statement": "On compare le calcul de `fibonacci(40)` avec\nla version récursive naïve et avec la\nversion mémoïsée. Quel est l'ordre de\ngrandeur du rapport entre les deux\ncomplexités ?",
      "options": [
        {
          "text": "Les deux versions ont la même complexité\nasymptotique\n",
          "correct": false,
          "feedback": "Erreur : la mémoïsation change\nfondamentalement la complexité. La\nversion naïve calcule plusieurs fois\nles mêmes valeurs (par exemple,\n$\\mathrm{fib}(35)$ est calculée\nplusieurs millions de fois) ; la\nmémoïsée ne calcule chaque valeur qu'une\nseule fois.\n"
        },
        {
          "text": "La version mémoïsée est environ deux fois\nplus rapide\n",
          "correct": false,
          "feedback": "Erreur : un facteur deux serait une\namélioration mineure. Or le passage de\n$O(2^n)$ à $O(n)$ représente un gain\n**exponentiel**, pas multiplicatif. Pour\n$n = 40$, c'est un facteur de plus de\ndix milliards.\n"
        },
        {
          "text": "La version mémoïsée est plus lente à\ncause du surcoût du cache\n",
          "correct": false,
          "feedback": "Erreur : pour $n$ aussi petit que $20$,\nle surcoût du cache est négligeable\ndevant le gain en évitant les calculs\nredondants. Pour $n = 40$, le gain est\ncolossal.\n"
        },
        {
          "text": "La version mémoïsée est environ\n$10^{10}$ fois plus rapide\n",
          "correct": true,
          "feedback": "Bonne réponse : version naïve en\n$O(2^n)$, soit environ\n$2^{40} \\approx 10^{12}$ appels\nrécursifs. Version mémoïsée en $O(n)$,\nsoit environ $40$ appels effectifs. Le\nrapport est donc d'environ\n$10^{12} / 40 \\approx 2{,}5 \\cdot 10^{10}$.\nEn pratique, on passe de plusieurs\nminutes à une fraction de milliseconde.\n"
        }
      ],
      "explanation": "Cet exemple illustre la puissance de la\nprogrammation dynamique. Bien sûr, pour\nFibonacci en pratique, on utilise plutôt la\nversion itérative à deux variables, qui a\nla même complexité $O(n)$ mais avec une\nconsommation mémoire $O(1)$ au lieu de\n$O(n)$."
    },
    {
      "id": "q32",
      "difficulty": 2,
      "skills": [
        "exemples",
        "escalier",
        "recurrence",
        "calcul"
      ],
      "title": "Monter un escalier une ou deux marches à la fois",
      "partie": "exemples",
      "statement": "Pour monter un escalier de $4$ marches, on avance d'une ou de\ndeux marches à chaque pas. Le nombre de façons $E(n)$ vérifie\n$E(n) = E(n-1) + E(n-2)$ avec $E(1) = 1$ et $E(2) = 2$. Combien de\nfaçons de monter $4$ marches ?",
      "options": [
        {
          "text": "$5$",
          "correct": true,
          "feedback": "Bonne réponse : en remplissant la table de proche en proche,\n$E(3) = 3$, puis $E(4) = 5$, et ainsi de suite jusqu'à\n$E(4) = 5$.\n"
        },
        {
          "text": "$3$",
          "correct": false,
          "feedback": "Erreur : $3$ est $E(3)$ ; il reste une étape de la\nrécurrence.\n"
        },
        {
          "text": "$16$",
          "correct": false,
          "feedback": "Erreur : $2^{4}$ compterait des suites de pas sans tenir\ncompte de la longueur totale.\n"
        },
        {
          "text": "$8$",
          "correct": false,
          "feedback": "Erreur : $8$ est $E(5)$, une marche de trop.\n"
        }
      ],
      "explanation": "La dernière marche est atteinte soit depuis la précédente, soit\ndepuis l'avant-dernière : la récurrence est celle de Fibonacci, et\nla tabulation évite tout recalcul.",
      "variants": [
        {
          "statement": "Pour monter un escalier de $4$ marches, on avance d'une ou de\ndeux marches à chaque pas. Le nombre de façons $E(n)$ vérifie\n$E(n) = E(n-1) + E(n-2)$ avec $E(1) = 1$ et $E(2) = 2$. Combien de\nfaçons de monter $4$ marches ?",
          "options": [
            {
              "text": "$5$",
              "correct": true,
              "feedback": "Bonne réponse : en remplissant la table de proche en proche,\n$E(3) = 3$, puis $E(4) = 5$, et ainsi de suite jusqu'à\n$E(4) = 5$.\n"
            },
            {
              "text": "$3$",
              "correct": false,
              "feedback": "Erreur : $3$ est $E(3)$ ; il reste une étape de la\nrécurrence.\n"
            },
            {
              "text": "$16$",
              "correct": false,
              "feedback": "Erreur : $2^{4}$ compterait des suites de pas sans tenir\ncompte de la longueur totale.\n"
            },
            {
              "text": "$8$",
              "correct": false,
              "feedback": "Erreur : $8$ est $E(5)$, une marche de trop.\n"
            }
          ],
          "explanation": "La dernière marche est atteinte soit depuis la précédente, soit\ndepuis l'avant-dernière : la récurrence est celle de Fibonacci, et\nla tabulation évite tout recalcul."
        },
        {
          "statement": "Pour monter un escalier de $5$ marches, on avance d'une ou de\ndeux marches à chaque pas. Le nombre de façons $E(n)$ vérifie\n$E(n) = E(n-1) + E(n-2)$ avec $E(1) = 1$ et $E(2) = 2$. Combien de\nfaçons de monter $5$ marches ?",
          "options": [
            {
              "text": "$8$",
              "correct": true,
              "feedback": "Bonne réponse : en remplissant la table de proche en proche,\n$E(3) = 3$, puis $E(4) = 5$, et ainsi de suite jusqu'à\n$E(5) = 8$.\n"
            },
            {
              "text": "$5$",
              "correct": false,
              "feedback": "Erreur : $5$ est $E(4)$ ; il reste une étape de la\nrécurrence.\n"
            },
            {
              "text": "$32$",
              "correct": false,
              "feedback": "Erreur : $2^{5}$ compterait des suites de pas sans tenir\ncompte de la longueur totale.\n"
            },
            {
              "text": "$13$",
              "correct": false,
              "feedback": "Erreur : $13$ est $E(6)$, une marche de trop.\n"
            }
          ],
          "explanation": "La dernière marche est atteinte soit depuis la précédente, soit\ndepuis l'avant-dernière : la récurrence est celle de Fibonacci, et\nla tabulation évite tout recalcul."
        },
        {
          "statement": "Pour monter un escalier de $6$ marches, on avance d'une ou de\ndeux marches à chaque pas. Le nombre de façons $E(n)$ vérifie\n$E(n) = E(n-1) + E(n-2)$ avec $E(1) = 1$ et $E(2) = 2$. Combien de\nfaçons de monter $6$ marches ?",
          "options": [
            {
              "text": "$13$",
              "correct": true,
              "feedback": "Bonne réponse : en remplissant la table de proche en proche,\n$E(3) = 3$, puis $E(4) = 5$, et ainsi de suite jusqu'à\n$E(6) = 13$.\n"
            },
            {
              "text": "$8$",
              "correct": false,
              "feedback": "Erreur : $8$ est $E(5)$ ; il reste une étape de la\nrécurrence.\n"
            },
            {
              "text": "$64$",
              "correct": false,
              "feedback": "Erreur : $2^{6}$ compterait des suites de pas sans tenir\ncompte de la longueur totale.\n"
            },
            {
              "text": "$21$",
              "correct": false,
              "feedback": "Erreur : $21$ est $E(7)$, une marche de trop.\n"
            }
          ],
          "explanation": "La dernière marche est atteinte soit depuis la précédente, soit\ndepuis l'avant-dernière : la récurrence est celle de Fibonacci, et\nla tabulation évite tout recalcul."
        }
      ]
    },
    {
      "id": "q33",
      "difficulty": 3,
      "skills": [
        "exemples",
        "sous-tableau",
        "maximum",
        "trace"
      ],
      "title": "La somme maximale d'une tranche",
      "partie": "exemples",
      "statement": "Pour trouver la tranche contiguë de somme maximale d'une liste, on\ncalcule pour chaque position $i$ la meilleure somme d'une tranche\nqui se termine en $i$ : $M(i) = t[i] + \\max(0, M(i-1))$. Quelle est\nla somme maximale pour `[-2, 3, -1, 4, -3]` ?",
      "options": [
        {
          "text": "$6$, obtenue par la tranche `[3, -1, 4]`",
          "correct": true,
          "feedback": "Bonne réponse : on prolonge la tranche précédente si sa somme est\npositive, sinon on repart de $t[i]$. Le maximum des $M(i)$ vaut\n$6$.\n"
        },
        {
          "text": "$1$, la somme de toute la liste",
          "correct": false,
          "feedback": "Erreur : la liste entière contient des valeurs négatives qui\nfont baisser la somme ; une tranche plus courte fait mieux.\n"
        },
        {
          "text": "$4$, le plus grand élément isolé",
          "correct": false,
          "feedback": "Erreur : une tranche peut regrouper plusieurs éléments et\ndépasser le maximum isolé, ou, si tout est négatif, s'y\nréduire.\n"
        },
        {
          "text": "$0$, en choisissant la tranche vide",
          "correct": false,
          "feedback": "Erreur : la tranche doit contenir au moins un élément.\n"
        }
      ],
      "explanation": "Chaque $M(i)$ se déduit du précédent en temps constant : une seule\npasse suffit, là où tester toutes les tranches coûterait $n^2$.",
      "variants": [
        {
          "statement": "Pour trouver la tranche contiguë de somme maximale d'une liste, on\ncalcule pour chaque position $i$ la meilleure somme d'une tranche\nqui se termine en $i$ : $M(i) = t[i] + \\max(0, M(i-1))$. Quelle est\nla somme maximale pour `[-2, 3, -1, 4, -3]` ?",
          "options": [
            {
              "text": "$6$, obtenue par la tranche `[3, -1, 4]`",
              "correct": true,
              "feedback": "Bonne réponse : on prolonge la tranche précédente si sa somme est\npositive, sinon on repart de $t[i]$. Le maximum des $M(i)$ vaut\n$6$.\n"
            },
            {
              "text": "$1$, la somme de toute la liste",
              "correct": false,
              "feedback": "Erreur : la liste entière contient des valeurs négatives qui\nfont baisser la somme ; une tranche plus courte fait mieux.\n"
            },
            {
              "text": "$4$, le plus grand élément isolé",
              "correct": false,
              "feedback": "Erreur : une tranche peut regrouper plusieurs éléments et\ndépasser le maximum isolé, ou, si tout est négatif, s'y\nréduire.\n"
            },
            {
              "text": "$0$, en choisissant la tranche vide",
              "correct": false,
              "feedback": "Erreur : la tranche doit contenir au moins un élément.\n"
            }
          ],
          "explanation": "Chaque $M(i)$ se déduit du précédent en temps constant : une seule\npasse suffit, là où tester toutes les tranches coûterait $n^2$."
        },
        {
          "statement": "Pour trouver la tranche contiguë de somme maximale d'une liste, on\ncalcule pour chaque position $i$ la meilleure somme d'une tranche\nqui se termine en $i$ : $M(i) = t[i] + \\max(0, M(i-1))$. Quelle est\nla somme maximale pour `[1, -5, 2, 3]` ?",
          "options": [
            {
              "text": "$5$, obtenue par la tranche `[2, 3]`",
              "correct": true,
              "feedback": "Bonne réponse : on prolonge la tranche précédente si sa somme est\npositive, sinon on repart de $t[i]$. Le maximum des $M(i)$ vaut\n$5$.\n"
            },
            {
              "text": "$1$, la somme de toute la liste",
              "correct": false,
              "feedback": "Erreur : la liste entière contient des valeurs négatives qui\nfont baisser la somme ; une tranche plus courte fait mieux.\n"
            },
            {
              "text": "$3$, le plus grand élément isolé",
              "correct": false,
              "feedback": "Erreur : une tranche peut regrouper plusieurs éléments et\ndépasser le maximum isolé, ou, si tout est négatif, s'y\nréduire.\n"
            },
            {
              "text": "$0$, en choisissant la tranche vide",
              "correct": false,
              "feedback": "Erreur : la tranche doit contenir au moins un élément.\n"
            }
          ],
          "explanation": "Chaque $M(i)$ se déduit du précédent en temps constant : une seule\npasse suffit, là où tester toutes les tranches coûterait $n^2$."
        },
        {
          "statement": "Pour trouver la tranche contiguë de somme maximale d'une liste, on\ncalcule pour chaque position $i$ la meilleure somme d'une tranche\nqui se termine en $i$ : $M(i) = t[i] + \\max(0, M(i-1))$. Quelle est\nla somme maximale pour `[-3, 2, -1, 2, -1]` ?",
          "options": [
            {
              "text": "$3$, obtenue par la tranche `[2, -1, 2]`",
              "correct": true,
              "feedback": "Bonne réponse : on prolonge la tranche précédente si sa somme est\npositive, sinon on repart de $t[i]$. Le maximum des $M(i)$ vaut\n$3$.\n"
            },
            {
              "text": "$-1$, la somme de toute la liste",
              "correct": false,
              "feedback": "Erreur : la liste entière contient des valeurs négatives qui\nfont baisser la somme ; une tranche plus courte fait mieux.\n"
            },
            {
              "text": "$2$, le plus grand élément isolé",
              "correct": false,
              "feedback": "Erreur : une tranche peut regrouper plusieurs éléments et\ndépasser le maximum isolé, ou, si tout est négatif, s'y\nréduire.\n"
            },
            {
              "text": "$0$, en choisissant la tranche vide",
              "correct": false,
              "feedback": "Erreur : la tranche doit contenir au moins un élément.\n"
            }
          ],
          "explanation": "Chaque $M(i)$ se déduit du précédent en temps constant : une seule\npasse suffit, là où tester toutes les tranches coûterait $n^2$."
        }
      ]
    },
    {
      "id": "q34",
      "difficulty": 3,
      "skills": [
        "exemples",
        "rendu-monnaie",
        "denombrement"
      ],
      "title": "Compter les façons de rendre une somme",
      "partie": "exemples",
      "statement": "Avec des pièces de $1$ et de $2$, de combien de façons peut-on\nrendre la somme $4$, l'ordre des pièces ne comptant pas ?",
      "options": [
        {
          "text": "$3$",
          "correct": true,
          "feedback": "Bonne réponse : le nombre de pièces de $2$ va de $0$ à\n$2$, le reste étant complété par des $1$ : $1+1+1+1$, $1+1+2$ et $2+2$.\n"
        },
        {
          "text": "$4$",
          "correct": false,
          "feedback": "Erreur : le nombre de façons n'est pas le montant.\n"
        },
        {
          "text": "$8$",
          "correct": false,
          "feedback": "Erreur : ce compte supposerait des choix indépendants, alors\nque le total est contraint.\n"
        },
        {
          "text": "$1$",
          "correct": false,
          "feedback": "Erreur : plusieurs répartitions entre pièces de $1$ et de $2$\nconviennent.\n"
        }
      ],
      "explanation": "Avec plus de valeurs de pièces, on remplit une table\n$N(s, k)$, nombre de façons de rendre $s$ avec les $k$ premières\npièces : chaque case se déduit des précédentes.",
      "variants": [
        {
          "statement": "Avec des pièces de $1$ et de $2$, de combien de façons peut-on\nrendre la somme $4$, l'ordre des pièces ne comptant pas ?",
          "options": [
            {
              "text": "$3$",
              "correct": true,
              "feedback": "Bonne réponse : le nombre de pièces de $2$ va de $0$ à\n$2$, le reste étant complété par des $1$ : $1+1+1+1$, $1+1+2$ et $2+2$.\n"
            },
            {
              "text": "$4$",
              "correct": false,
              "feedback": "Erreur : le nombre de façons n'est pas le montant.\n"
            },
            {
              "text": "$8$",
              "correct": false,
              "feedback": "Erreur : ce compte supposerait des choix indépendants, alors\nque le total est contraint.\n"
            },
            {
              "text": "$1$",
              "correct": false,
              "feedback": "Erreur : plusieurs répartitions entre pièces de $1$ et de $2$\nconviennent.\n"
            }
          ],
          "explanation": "Avec plus de valeurs de pièces, on remplit une table\n$N(s, k)$, nombre de façons de rendre $s$ avec les $k$ premières\npièces : chaque case se déduit des précédentes."
        },
        {
          "statement": "Avec des pièces de $1$ et de $2$, de combien de façons peut-on\nrendre la somme $6$, l'ordre des pièces ne comptant pas ?",
          "options": [
            {
              "text": "$4$",
              "correct": true,
              "feedback": "Bonne réponse : le nombre de pièces de $2$ va de $0$ à\n$3$, le reste étant complété par des $1$ : $1+1+1+1+1+1$, $1+1+1+1+2$, $1+1+2+2$ et $2+2+2$.\n"
            },
            {
              "text": "$6$",
              "correct": false,
              "feedback": "Erreur : le nombre de façons n'est pas le montant.\n"
            },
            {
              "text": "$16$",
              "correct": false,
              "feedback": "Erreur : ce compte supposerait des choix indépendants, alors\nque le total est contraint.\n"
            },
            {
              "text": "$1$",
              "correct": false,
              "feedback": "Erreur : plusieurs répartitions entre pièces de $1$ et de $2$\nconviennent.\n"
            }
          ],
          "explanation": "Avec plus de valeurs de pièces, on remplit une table\n$N(s, k)$, nombre de façons de rendre $s$ avec les $k$ premières\npièces : chaque case se déduit des précédentes."
        },
        {
          "statement": "Avec des pièces de $1$ et de $2$, de combien de façons peut-on\nrendre la somme $8$, l'ordre des pièces ne comptant pas ?",
          "options": [
            {
              "text": "$5$",
              "correct": true,
              "feedback": "Bonne réponse : le nombre de pièces de $2$ va de $0$ à\n$4$, le reste étant complété par des $1$ : de quatre pièces de $2$ à huit pièces de $1$, selon le nombre de pièces de $2$.\n"
            },
            {
              "text": "$8$",
              "correct": false,
              "feedback": "Erreur : le nombre de façons n'est pas le montant.\n"
            },
            {
              "text": "$32$",
              "correct": false,
              "feedback": "Erreur : ce compte supposerait des choix indépendants, alors\nque le total est contraint.\n"
            },
            {
              "text": "$1$",
              "correct": false,
              "feedback": "Erreur : plusieurs répartitions entre pièces de $1$ et de $2$\nconviennent.\n"
            }
          ],
          "explanation": "Avec plus de valeurs de pièces, on remplit une table\n$N(s, k)$, nombre de façons de rendre $s$ avec les $k$ premières\npièces : chaque case se déduit des précédentes."
        }
      ]
    },
    {
      "id": "q35",
      "difficulty": 2,
      "skills": [
        "exemples",
        "coefficients-binomiaux",
        "pascal"
      ],
      "title": "Le triangle de Pascal par tabulation",
      "partie": "exemples",
      "statement": "Les coefficients binomiaux vérifient\n$C(n, k) = C(n-1, k-1) + C(n-1, k)$ avec $C(n, 0) = C(n, n) = 1$.\nEn remplissant la table ligne par ligne, que vaut $C(6, 2)$ ?",
      "options": [
        {
          "text": "$15$",
          "correct": true,
          "feedback": "Bonne réponse : $C(6, 2) = C(5, 1) + C(5, 2) =\n5 + 10 = 15$, chaque terme venant de la ligne précédente.\n"
        },
        {
          "text": "$8$",
          "correct": false,
          "feedback": "Erreur : la récurrence additionne deux coefficients, pas $n$ et\n$k$.\n"
        },
        {
          "text": "$12$",
          "correct": false,
          "feedback": "Erreur : $6 \\times 2$ n'a rien à voir avec la récurrence.\n"
        },
        {
          "text": "$10$",
          "correct": false,
          "feedback": "Erreur : $10$ est $C(5, 2)$, il manque l'autre terme.\n"
        }
      ],
      "explanation": "Calculer $C(n, k)$ par la récurrence sans table recalcule les mêmes\nvaleurs ; la tabulation construit chaque ligne à partir de la\nprécédente.",
      "variants": [
        {
          "statement": "Les coefficients binomiaux vérifient\n$C(n, k) = C(n-1, k-1) + C(n-1, k)$ avec $C(n, 0) = C(n, n) = 1$.\nEn remplissant la table ligne par ligne, que vaut $C(6, 2)$ ?",
          "options": [
            {
              "text": "$15$",
              "correct": true,
              "feedback": "Bonne réponse : $C(6, 2) = C(5, 1) + C(5, 2) =\n5 + 10 = 15$, chaque terme venant de la ligne précédente.\n"
            },
            {
              "text": "$8$",
              "correct": false,
              "feedback": "Erreur : la récurrence additionne deux coefficients, pas $n$ et\n$k$.\n"
            },
            {
              "text": "$12$",
              "correct": false,
              "feedback": "Erreur : $6 \\times 2$ n'a rien à voir avec la récurrence.\n"
            },
            {
              "text": "$10$",
              "correct": false,
              "feedback": "Erreur : $10$ est $C(5, 2)$, il manque l'autre terme.\n"
            }
          ],
          "explanation": "Calculer $C(n, k)$ par la récurrence sans table recalcule les mêmes\nvaleurs ; la tabulation construit chaque ligne à partir de la\nprécédente."
        },
        {
          "statement": "Les coefficients binomiaux vérifient\n$C(n, k) = C(n-1, k-1) + C(n-1, k)$ avec $C(n, 0) = C(n, n) = 1$.\nEn remplissant la table ligne par ligne, que vaut $C(6, 3)$ ?",
          "options": [
            {
              "text": "$20$",
              "correct": true,
              "feedback": "Bonne réponse : $C(6, 3) = C(5, 2) + C(5, 3) =\n10 + 10 = 20$, chaque terme venant de la ligne précédente.\n"
            },
            {
              "text": "$9$",
              "correct": false,
              "feedback": "Erreur : la récurrence additionne deux coefficients, pas $n$ et\n$k$.\n"
            },
            {
              "text": "$18$",
              "correct": false,
              "feedback": "Erreur : $6 \\times 3$ n'a rien à voir avec la récurrence.\n"
            },
            {
              "text": "$10$",
              "correct": false,
              "feedback": "Erreur : $10$ est $C(5, 3)$, il manque l'autre terme.\n"
            }
          ],
          "explanation": "Calculer $C(n, k)$ par la récurrence sans table recalcule les mêmes\nvaleurs ; la tabulation construit chaque ligne à partir de la\nprécédente."
        },
        {
          "statement": "Les coefficients binomiaux vérifient\n$C(n, k) = C(n-1, k-1) + C(n-1, k)$ avec $C(n, 0) = C(n, n) = 1$.\nEn remplissant la table ligne par ligne, que vaut $C(7, 2)$ ?",
          "options": [
            {
              "text": "$21$",
              "correct": true,
              "feedback": "Bonne réponse : $C(7, 2) = C(6, 1) + C(6, 2) =\n6 + 15 = 21$, chaque terme venant de la ligne précédente.\n"
            },
            {
              "text": "$9$",
              "correct": false,
              "feedback": "Erreur : la récurrence additionne deux coefficients, pas $n$ et\n$k$.\n"
            },
            {
              "text": "$14$",
              "correct": false,
              "feedback": "Erreur : $7 \\times 2$ n'a rien à voir avec la récurrence.\n"
            },
            {
              "text": "$15$",
              "correct": false,
              "feedback": "Erreur : $15$ est $C(6, 2)$, il manque l'autre terme.\n"
            }
          ],
          "explanation": "Calculer $C(n, k)$ par la récurrence sans table recalcule les mêmes\nvaleurs ; la tabulation construit chaque ligne à partir de la\nprécédente."
        }
      ]
    },
    {
      "id": "q36",
      "difficulty": 2,
      "skills": [
        "principe",
        "chevauchement",
        "tri-fusion"
      ],
      "title": "Quand la programmation dynamique n'apporte rien",
      "partie": "principe",
      "statement": "Le tri fusion découpe la liste en deux moitiés et les trie\nrécursivement. La programmation dynamique peut-elle l'accélérer ?",
      "options": [
        {
          "text": "Non, aucun sous-problème n'y est résolu deux fois",
          "correct": true,
          "feedback": "Bonne réponse : chaque moitié n'est triée qu'une fois. Sans\nchevauchement de sous-problèmes, mémoriser les résultats ne sert\nà rien.\n"
        },
        {
          "text": "Oui, en mémorisant chaque fusion dans un dictionnaire",
          "correct": false,
          "feedback": "Erreur : chaque fusion n'est faite qu'une fois ; la mémoriser\ncoûterait sans rien économiser.\n"
        },
        {
          "text": "Oui, tout algorithme récursif en profite de la même façon",
          "correct": false,
          "feedback": "Erreur : seuls ceux dont les sous-problèmes se répètent en\nprofitent.\n"
        },
        {
          "text": "Non, car le tri fusion n'est pas un algorithme récursif",
          "correct": false,
          "feedback": "Erreur : il l'est ; la raison est l'absence de chevauchement.\n"
        }
      ],
      "explanation": "Diviser pour régner et programmation dynamique se distinguent par\nle chevauchement des sous-problèmes : absent dans le premier,\ncentral dans la seconde."
    },
    {
      "id": "q37",
      "difficulty": 3,
      "skills": [
        "principe",
        "recalculs",
        "fibonacci",
        "denombrement"
      ],
      "title": "Combien de fois le même sous-problème",
      "partie": "principe",
      "statement": "Dans le calcul récursif naïf de `fib(6)`, combien de fois\n`fib(2)` est-il calculé ?\n```python\ndef fib(n):\n    if n <= 1:\n        return n\n    return fib(n - 1) + fib(n - 2)\n```",
      "options": [
        {
          "text": "$5$ fois",
          "correct": true,
          "feedback": "Bonne réponse : le nombre d'appels à `fib(2)` dans `fib(n)` est\n$F(n-1)$, le terme de Fibonacci lui-même : $5$ pour\n$n = 6$. Un seul calcul suffirait.\n"
        },
        {
          "text": "$1$ fois",
          "correct": false,
          "feedback": "Erreur : c'est le cas avec mémoïsation ; sans elle, l'arbre\nd'appels répète ce sous-problème.\n"
        },
        {
          "text": "$12$ fois",
          "correct": false,
          "feedback": "Erreur : le compte ne suit pas une loi linéaire en $n$.\n"
        },
        {
          "text": "$6$ fois",
          "correct": false,
          "feedback": "Erreur : le compte suit la suite de Fibonacci, pas $n$.\n"
        }
      ],
      "explanation": "Le chevauchement des sous-problèmes se mesure : ici les petits\ntermes sont recalculés un nombre exponentiel de fois.",
      "variants": [
        {
          "statement": "Dans le calcul récursif naïf de `fib(6)`, combien de fois\n`fib(2)` est-il calculé ?\n```python\ndef fib(n):\n    if n <= 1:\n        return n\n    return fib(n - 1) + fib(n - 2)\n```",
          "options": [
            {
              "text": "$5$ fois",
              "correct": true,
              "feedback": "Bonne réponse : le nombre d'appels à `fib(2)` dans `fib(n)` est\n$F(n-1)$, le terme de Fibonacci lui-même : $5$ pour\n$n = 6$. Un seul calcul suffirait.\n"
            },
            {
              "text": "$1$ fois",
              "correct": false,
              "feedback": "Erreur : c'est le cas avec mémoïsation ; sans elle, l'arbre\nd'appels répète ce sous-problème.\n"
            },
            {
              "text": "$12$ fois",
              "correct": false,
              "feedback": "Erreur : le compte ne suit pas une loi linéaire en $n$.\n"
            },
            {
              "text": "$6$ fois",
              "correct": false,
              "feedback": "Erreur : le compte suit la suite de Fibonacci, pas $n$.\n"
            }
          ],
          "explanation": "Le chevauchement des sous-problèmes se mesure : ici les petits\ntermes sont recalculés un nombre exponentiel de fois."
        },
        {
          "statement": "Dans le calcul récursif naïf de `fib(7)`, combien de fois\n`fib(2)` est-il calculé ?\n```python\ndef fib(n):\n    if n <= 1:\n        return n\n    return fib(n - 1) + fib(n - 2)\n```",
          "options": [
            {
              "text": "$8$ fois",
              "correct": true,
              "feedback": "Bonne réponse : le nombre d'appels à `fib(2)` dans `fib(n)` est\n$F(n-1)$, le terme de Fibonacci lui-même : $8$ pour\n$n = 7$. Un seul calcul suffirait.\n"
            },
            {
              "text": "$1$ fois",
              "correct": false,
              "feedback": "Erreur : c'est le cas avec mémoïsation ; sans elle, l'arbre\nd'appels répète ce sous-problème.\n"
            },
            {
              "text": "$14$ fois",
              "correct": false,
              "feedback": "Erreur : le compte ne suit pas une loi linéaire en $n$.\n"
            },
            {
              "text": "$7$ fois",
              "correct": false,
              "feedback": "Erreur : le compte suit la suite de Fibonacci, pas $n$.\n"
            }
          ],
          "explanation": "Le chevauchement des sous-problèmes se mesure : ici les petits\ntermes sont recalculés un nombre exponentiel de fois."
        },
        {
          "statement": "Dans le calcul récursif naïf de `fib(5)`, combien de fois\n`fib(2)` est-il calculé ?\n```python\ndef fib(n):\n    if n <= 1:\n        return n\n    return fib(n - 1) + fib(n - 2)\n```",
          "options": [
            {
              "text": "$3$ fois",
              "correct": true,
              "feedback": "Bonne réponse : le nombre d'appels à `fib(2)` dans `fib(n)` est\n$F(n-1)$, le terme de Fibonacci lui-même : $3$ pour\n$n = 5$. Un seul calcul suffirait.\n"
            },
            {
              "text": "$1$ fois",
              "correct": false,
              "feedback": "Erreur : c'est le cas avec mémoïsation ; sans elle, l'arbre\nd'appels répète ce sous-problème.\n"
            },
            {
              "text": "$10$ fois",
              "correct": false,
              "feedback": "Erreur : le compte ne suit pas une loi linéaire en $n$.\n"
            },
            {
              "text": "$5$ fois",
              "correct": false,
              "feedback": "Erreur : le compte suit la suite de Fibonacci, pas $n$.\n"
            }
          ],
          "explanation": "Le chevauchement des sous-problèmes se mesure : ici les petits\ntermes sont recalculés un nombre exponentiel de fois."
        }
      ]
    },
    {
      "id": "q38",
      "difficulty": 1,
      "skills": [
        "principe",
        "recurrence",
        "equation"
      ],
      "title": "Le point de départ d'une solution dynamique",
      "partie": "principe",
      "statement": "Par quoi commence la conception d'une solution par programmation\ndynamique ?",
      "options": [
        {
          "text": "Par une relation de récurrence entre sous-problèmes",
          "correct": true,
          "feedback": "Bonne réponse : sans récurrence, rien à mémoriser ni à tabuler.\nLe choix de la table et l'ordre de remplissage en découlent.\n"
        },
        {
          "text": "Par le choix du langage et des structures de données",
          "correct": false,
          "feedback": "Erreur : la méthode est indépendante du langage.\n"
        },
        {
          "text": "Par la mesure du temps d'exécution de la version naïve",
          "correct": false,
          "feedback": "Erreur : mesurer peut motiver, mais ne construit pas la\nsolution.\n"
        },
        {
          "text": "Par le tri des données d'entrée par ordre croissant",
          "correct": false,
          "feedback": "Erreur : le tri n'est pas une étape générale de la méthode.\n"
        }
      ],
      "explanation": "Récurrence, table, ordre de remplissage, lecture du résultat : les\nquatre temps d'une programmation dynamique."
    },
    {
      "id": "q39",
      "difficulty": 2,
      "skills": [
        "memoisation",
        "cle",
        "deux-arguments"
      ],
      "title": "Mémoïser une fonction à deux arguments",
      "partie": "techniques",
      "statement": "La fonction `valeur(i, w)` du sac à dos dépend de deux entiers.\nComment organiser sa mémoïsation avec un dictionnaire `memo` ?",
      "options": [
        {
          "text": "Avec la clé `(i, w)`, un tuple",
          "correct": true,
          "feedback": "Bonne réponse : un tuple est immuable et identifie le couple\nd'arguments ; `memo[(i, w)]` retrouve le résultat déjà calculé.\n"
        },
        {
          "text": "Avec la clé `i` seulement, `w` variant peu",
          "correct": false,
          "feedback": "Erreur : deux appels de même `i` et de `w` différents auraient\ndes résultats différents mais la même clé.\n"
        },
        {
          "text": "Avec la clé `i + w`, la somme des deux",
          "correct": false,
          "feedback": "Erreur : `(2, 3)` et `(3, 2)` auraient la même clé pour des\nrésultats différents.\n"
        },
        {
          "text": "Avec une liste `[i, w]` comme clé",
          "correct": false,
          "feedback": "Erreur : une liste n'est pas utilisable comme clé de\ndictionnaire.\n"
        }
      ],
      "explanation": "La clé de mémoïsation doit déterminer entièrement l'appel : un\ntuple de tous les arguments, ou une table à deux dimensions."
    }
  ]
}