Épreuve écrite · 2026 · Jour 1

Bac NSI 2026 Amérique du Nord jour 1

Un jeu de Puissance 4, une salle de jeux en réseau et une rue face à la mer : trois contextes pour comprendre les arbres de décisions, les files et la programmation dynamique. Les corrections explicitent le modèle utilisé et les ambiguïtés du squelette min-max, afin de produire des programmes cohérents. Épreuve de 3 h 30 sans calculatrice, trois exercices indépendants.

Dans ce sujet

Un corrigé pédagogique pour comprendre et justifier vos réponses. Les conseils de rédaction ne constituent pas un barème officiel détaillé.

Exercice 1 · 6 points

Puissance 4 : scores, arbre de coups et min-max

Deux joueurs introduisent alternativement un pion blanc ou noir dans l’une des sept colonnes d’une grille verticale de six lignes. Le pion tombe jusqu’à la première case libre en partant du bas. Le premier alignement d’au moins quatre pions de même couleur, horizontal, vertical ou diagonal, gagne. Lignes 0 à 5 de haut en bas ; colonnes 0 à 6 de gauche à droite. La figure 1 montre la victoire du blanc sur une diagonale :

Ligne0123456
0videvidevidevidevidevidevide
1videvidevidevidevidevidevide
2videvidevidevideblancvidevide
3videvidevideblancnoirvidevide
4videnoirblancblancnoirvidevide
5videblancnoirnoirnoirblancvide

On veut programmer min-max : simuler des futurs coups des deux joueurs, donner un score aux possibilités puis choisir la meilleure en tenant compte de la réponse adverse. La classe Grille possède un unique attribut grille, une liste de six listes de sept entiers : 0 pour vide, 1 pour le joueur 1, 2 pour le joueur 2.

Question 1

#

Écrire le constructeur de Grille, dont le tableau est initialement rempli de 0.

Indice

La grille comporte six lignes indépendantes de sept cases.

Comprendre la correction
def __init__(self):
    self.grille = [[0 for _ in range(7)] for _ in range(6)]

Chaque itération externe crée une nouvelle ligne. Ne pas écrire [[0]*7]*6 : les six entrées référenceraient alors la même liste et jouer un pion modifierait simultanément plusieurs lignes. La compréhension crée six objets distincts, ce qui est nécessaire pour représenter des cases indépendantes.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)
def joue(self, colonne, joueur):
    ligne = 5
    while ligne != -1 and self.grille[ligne][colonne] != 0:
        ligne = ...
    if ligne != -1:
        self.grille[ligne][colonne] = ...
        return ...
    else:
        return ...

Question 2

#

Compléter joue(colonne, joueur), qui place le pion de bas en haut si la colonne n’est pas pleine et renvoie True, sinon False.

Indice

Une case occupée fait diminuer l’indice de ligne.

Comprendre la correction
def joue(self, colonne, joueur):
    ligne = 5
    while ligne != -1 and self.grille[ligne][colonne] != 0:
        ligne = ligne - 1
    if ligne != -1:
        self.grille[ligne][colonne] = joueur
        return True
    else:
        return False

On part du bas et remonte les cases occupées. Le test ligne != -1 doit être évalué avant l’accès : Python court-circuite AND, donc on ne lit pas accidentellement la dernière ligne via l’indice -1 lorsque la colonne est pleine. L’échec ne modifie aucune case. Les paramètres sont supposés valides : colonne de 0 à 6 et joueur 1 ou 2.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)
[
  [
    0,
    0,
    0,
    0,
    0,
    0,
    0
  ],
  [
    0,
    0,
    0,
    0,
    0,
    0,
    0
  ],
  [
    0,
    0,
    0,
    0,
    0,
    0,
    0
  ],
  [
    0,
    0,
    0,
    0,
    0,
    0,
    0
  ],
  [
    0,
    0,
    0,
    1,
    0,
    0,
    0
  ],
  [
    0,
    0,
    1,
    2,
    0,
    0,
    0
  ]
]

Question 3

#

Créer jeu1 correspondant au tableau donné, uniquement avec les méthodes de Grille.

Indice

Le pion en (4,3) ne peut être posé qu’après celui en (5,3).

Comprendre la correction
jeu1 = Grille()
jeu1.joue(2, 1)
jeu1.joue(3, 2)
jeu1.joue(3, 1)

Le joueur 1 remplit d’abord la case (5,2). Le joueur 2 joue en (5,3), puis le pion suivant du joueur 1 tombe en (4,3), car le bas de la colonne 3 est déjà occupé. L’ordre de ces trois appels respecte l’alternance des joueurs et la gravité.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

valeur_case(ligne,colonne) est fournie et compte les alignements de quatre cases contenant cette position. La figure 2 donne valeur_case(0,1)=4 : deux horizontaux (0,0)-(0,3) et (0,1)-(0,4), un vertical (0,1)-(3,1), une diagonale (0,1),(1,2),(2,3),(3,4). Le score ajoute ces valeurs pour les pions 2 et les soustrait pour les pions 1.

Question 4

#

Calculer le score de jeu1 en détaillant les contributions.

Indice

Compte chaque segment de quatre cases qui inclut la case, quelle que soit l’occupation des autres cases.

Comprendre la correction

Score = valeur_case(5,3) - valeur_case(5,2) - valeur_case(4,3) = 7 - 5 - 10 = -8. Les pions du joueur 2 contribuent positivement, ceux du joueur 1 négativement. Les valeurs des cases ne dépendent que de leur position géométrique, pas des pions qui bloquent effectivement des alignements : c’est une heuristique de position, pas un compte de victoires déjà réalisables.

CaseAlignements horizontauxVerticauxDiagonauxValeur
(5,3)4127
(5,2)3115
(4,3)42410
Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

Question 5

#

Écrire score(self).

Indice

Le joueur 2 ajoute un poids et le joueur 1 retire le même poids.

Comprendre la correction
def score(self):
    total = 0
    for ligne in range(6):
        for colonne in range(7):
            if self.grille[ligne][colonne] == 2:
                total += valeur_case(ligne, colonne)
            elif self.grille[ligne][colonne] == 1:
                total -= valeur_case(ligne, colonne)
    return total

Les cases vides n’ajoutent rien. Deux boucles visitent les 42 cases, et l’accumulateur ne change pas la grille. Une grille vide a score 0 ; intervertir partout les joueurs inverse le signe du score. Cette propriété fournit un test utile et explique pourquoi un joueur minimise tandis que l’autre maximise.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

Partie B : arbre de coups

La profondeur est limitée par la constante globale niveau_max ; la racine est au niveau 0. Une victoire du joueur 1 vaut -(100 + 10×(niveau_max-niveau)), une victoire du joueur 2 l’opposé positif. À la limite de profondeur, on utilise le score de la grille. Sinon le joueur 1 choisit le minimum, le joueur 2 le maximum. Le bonus de profondeur favorise une victoire plus rapide. La figure 3 est un arbre partiel : racine -1 ; enfants de colonnes 0,1,…,6 au niveau 1 pour le joueur 1 ; sous le premier enfant, colonnes 0 et 1 visibles au niveau 2 pour le joueur 2.

Racine : colonne -1
├─ colonne 0 (joueur 1)
│  ├─ colonne 0 (joueur 2)
│  └─ colonne 1 (joueur 2)
├─ colonne 1 (joueur 1)
├─ …
└─ colonne 6 (joueur 1)

Question 6

#

Compléter le constructeur de Noeud(colonne), avec score nul et aucun fils.

Indice

Les trois attributs sont explicitement donnés dans le sujet.

Comprendre la correction
class Noeud:
    def __init__(self, colonne):
        self.colonne = colonne
        self.score = 0
        self.suivants = []

Une liste propre à chaque instance recueille ses coups suivants. La racine utilise la colonne -1, car elle représente la position avant choix et pas un coup réellement joué. Les nœuds enfants stockent les colonnes de 0 à 6 correspondant aux coups autorisés.

Voir la question dans le sujet PDF, p. 5 (nouvel onglet)

On suppose disponible une méthode colonne_score_max analogue pour le maximum.

Question 7

#

Écrire colonne_score_min, qui renvoie (colonne, score) pour un fils au score minimal, sachant qu’au moins un fils existe.

Indice

Les coups légaux ne correspondent pas forcément aux indices 0..len(suivants)-1.

Comprendre la correction
def colonne_score_min(self):
    meilleur = self.suivants[0]
    for fils in self.suivants:
        if fils.score < meilleur.score:
            meilleur = fils
    return (meilleur.colonne, meilleur.score)

L’initialisation se fait avec un vrai fils, pas avec un score arbitraire comme 0 qui pourrait être inférieur à tous les scores. Avec un test strict, le premier fils minimal est conservé en cas d’égalité, ce qui respecte « un des fils ». Le couple doit contenir sa colonne réelle, pas son indice dans suivants : des colonnes pleines peuvent manquer dans cette liste.

Voir la question dans le sujet PDF, p. 5 (nouvel onglet)

Grille possède gagnant(), qui renvoie 0, 1 ou 2, et copie_grille(), dont les modifications ne touchent pas la grille d’origine. Le sujet suppose au plus un gagnant.

def calcule_score(self, niveau, joueur, grille):
    g = grille.gagnant()
    if g == 1:
        self.score = ...
    elif g == 2:
        self.score = ...
    elif niveau == niveau_max:
        self.score = ...
    else:
        for colonne in range(7):
            grille2 = grille.copie_grille()
            if grille2.joue(colonne, joueur):
                nouveau_noeud = ...
                self.suivants.append(...)
                nouveau_noeud.calcule_score(...)
        if joueur == 1:
            self.score = ...
        else:
            self.score = ...

Question 8

#

Compléter calcule_score conformément à min-max.

Indice

Les enfants doivent simuler l’adversaire à la profondeur suivante, pas rejouer toujours le même joueur.

Comprendre la correction

Convention nécessaire : le squelette joue avec joueur pour créer les enfants. Dans la version cohérente ci-dessous, ce paramètre désigne donc le joueur dont c’est le tour dans la grille reçue, et non celui qui vient de jouer comme l’affirme une phrase de la question. Le joueur alterne avec 3-joueur dans les sous-appels. Cette convention rend compatibles le choix min/max et les coups générés.

def calcule_score(self, niveau, joueur, grille):
    g = grille.gagnant()
    if g == 1:
        self.score = -(100 + 10 * (niveau_max - niveau))
    elif g == 2:
        self.score = 100 + 10 * (niveau_max - niveau)
    elif niveau == niveau_max:
        self.score = grille.score()
    else:
        for colonne in range(7):
            grille2 = grille.copie_grille()
            if grille2.joue(colonne, joueur):
                nouveau_noeud = Noeud(colonne)
                self.suivants.append(nouveau_noeud)
                nouveau_noeud.calcule_score(niveau + 1, 3 - joueur, grille2)
        if not self.suivants:
            self.score = 0  # grille pleine sans gagnant : nul
        elif joueur == 1:
            self.score = self.colonne_score_min()[1]
        else:
            self.score = self.colonne_score_max()[1]

On teste une victoire avant la limite de profondeur pour lui donner sa valeur terminale. Pour chaque colonne, une copie indépendante protège la grille du parent : les branches doivent partir de la même position. Seuls les coups légaux créent un enfant. Le score de l’enfant est calculé avant de sélectionner le minimum ou maximum. Le couple renvoyé par colonne_score_min/max fournit le score à l’indice 1.

Le squelette ne traite pas la grille pleine sans gagnant. La branche ajoutée if not self.suivants attribue 0 à cette partie nulle et évite de demander un minimum sur une liste vide. On suppose un nouvel arbre à chaque décision ; réutiliser les mêmes nœuds nécessiterait aussi de vider leurs anciens enfants. Une autre convention « joueur précédent » est possible, mais elle impose de changer la ligne qui joue et le choix du min/max de façon cohérente.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)

Question 9

#

Pourquoi explorer jusqu’à niveau_max = 42 est-il irréaliste ?

Indice

Un niveau supplémentaire multiplie approximativement le travail par le nombre de coups possibles.

Comprendre la correction

Chaque niveau possède jusqu’à sept choix. Le nombre de nœuds peut donc croître comme 1 + 7 + 7² + … + 7⁴², dont le dernier terme dépasse 10³⁵. Les colonnes pleines et les victoires réduisent le nombre réel de branches, mais l’espace à explorer reste immense. Chaque nœud demande aussi une copie de grille et un calcul. Une profondeur limitée avec heuristique est donc nécessaire pour une réponse rapide ; l’élagage alpha-bêta est une amélioration possible sans changer le résultat d’une profondeur donnée.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)

Question 10

#

Écrire choisit_coup(grille,joueur), qui renvoie la colonne à jouer.

Indice

Le score est la deuxième composante, la colonne à renvoyer la première.

Comprendre la correction
def choisit_coup(grille, joueur):
    racine = Noeud(-1)
    racine.calcule_score(0, joueur, grille)
    if not racine.suivants:
        return -1  # aucun coup : partie terminée ou profondeur nulle
    if joueur == 1:
        return racine.colonne_score_min()[0]
    return racine.colonne_score_max()[0]

L’arbre commence à la position courante au niveau 0, en passant le joueur qui doit agir selon la convention de la correction précédente. On choisit ensuite la colonne de l’enfant optimal, à l’indice 0 du couple. La fonction ne joue pas le coup : elle recommande une colonne. On suppose normalement une partie non terminée et niveau_max ≥ 1 ; -1 exprime explicitement l’absence de coup dans les autres cas.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)
Faire jouer min-max sur une vraie positionUn atelier pour expérimenter

Choisissez la position, le joueur à jouer et la profondeur. Le tableau montre le score garanti de chaque colonne si l’adversaire répond au mieux ; la grille reste celle du départ.

Lire le résultat de l’expérience initiale

Colonne conseillée : 3

Les branches alternent les joueurs. Les scores négatifs favorisent le joueur 1 ; les positifs le joueur 2. Une victoire plus rapide reçoit un score terminal plus marqué.

ColonneScore min-max
0-18
1-17
2-13
3-8
4-16
5-17
6-18

Min-max anticipe une réponse adverse optimale. Sa correction dépend autant de l’alternance des joueurs que du score choisi.

Revoir les notions de cet exercice

Exercice 2 · 6 points

Gamerzz : sous-réseaux et files de paquets

Gamerzz propose une salle de jeux en ligne, une salle de réalité virtuelle et un site pour réserver, organiser des événements et suivre les classements. Son architecture comporte sept routeurs A à G. A donne accès à Internet ; le réseau DMZ relie A et B. Les réseaux de service et les liens entre routeurs sont reconstruits ci-dessous.

RéseauCIDRRouteur
DMZ10.42.0.32/27A et B ; A = 10.42.0.33
Gaming Online10.42.0.64/28C
Administration10.42.0.80/29E
Application10.42.0.88/29D
Gaming VR10.42.0.96/27F
SGBD10.42.0.128/29G
LiaisonSous-réseau
B-C10.42.0.0/30
B-D10.42.0.4/30
B-E10.42.0.8/30
C-D10.42.0.12/30
C-F10.42.0.16/30
D-E10.42.0.20/30
D-G10.42.0.24/30
E-G10.42.0.28/30
AFCBEDG
Topologie des routeurs de Gamerzz
Lire les connexions du schéma
  • A relié à B
  • B relié à C
  • B relié à D
  • B relié à E
  • C relié à D
  • C relié à F
  • D relié à E
  • D relié à G
  • E relié à G

CIDR fixe les bits réseau. Les adresses ayant tous les bits machine à 0 ou tous à 1 sont réservées respectivement au réseau et à la diffusion.

Question 1

#

Donner le nombre d’adresses machines du réseau Administration et une adresse possible pour E.

Indice

Le réseau commence au multiple de huit .80.

Comprendre la correction

6 adresses utilisables : un /29 laisse trois bits, donc 2³ - 2 = 6. Le bloc va de 10.42.0.80 à .87 ; les hôtes vont de 10.42.0.81 à 10.42.0.86. E peut recevoir par exemple 10.42.0.81. Les .80 et .87 sont réservées.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

Question 2

#

Dans quel réseau se trouve la machine 10.42.0.70, à l’origine de téléchargements non autorisés ?

Indice

Un /28 représente un bloc de seize adresses.

Comprendre la correction

Gaming Online : son réseau 10.42.0.64/28 couvre les adresses .64 à .79, avec des hôtes .65 à .78. .70 appartient à cet intervalle. L’adresse ne permet pas à elle seule d’identifier une personne.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

Question 3

#

Les adresses utilisables des /30 sont attribuées par ordre alphabétique des routeurs. Donner celles de C/F sur 10.42.0.16/30 et C/D sur 10.42.0.12/30.

Indice

Ajoutez 1 et 2 à l’adresse réseau, jamais 0 et 3.

Comprendre la correction
LiaisonPremier routeurSecond routeur
C-FC : 10.42.0.17F : 10.42.0.18
C-DC : 10.42.0.13D : 10.42.0.14

Un /30 contient quatre adresses : réseau, deux hôtes, diffusion. C reçoit la première adresse hôte puisqu’il précède F comme D dans l’alphabet. Les routeurs ont plusieurs interfaces, donc C possède bien deux adresses distinctes selon le lien.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

RIP minimise le nombre de routeurs traversés. Table fournie pour B :

RéseauPasserelleSauts
DMZconnecté0
Gaming Online10.42.0.21
Internet10.42.0.331
Gaming VR10.42.0.22
Administration10.42.0.101
Application10.42.0.61
SGBD10.42.0.62

Question 4

#

Donner une table RIP possible pour C.

Indice

C touche directement B, D et F ; raisonnez ensuite une couche plus loin.

Comprendre la correction
RéseauPasserelle depuis CSauts
Gaming Onlineconnecté0
Gaming VR10.42.0.18 (F)1
DMZ10.42.0.1 (B)1
Internet10.42.0.1 (B)2
Administration10.42.0.1 (B) ou 10.42.0.14 (D)2
Application10.42.0.14 (D)1
SGBD10.42.0.14 (D)2

La table reprend les mêmes destinations de service que celle de B. La route vers l’Administration a deux choix ex æquo, via B-E ou D-E. Le prochain saut est toujours une interface directement reliée à C. Le routeur ne remplace pas la passerelle par l’adresse de la destination finale.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)
LiaisonDébit de la figure 2
A-B10 Gbit/s
B-C1 Gbit/s
B-D100 Mbit/s
B-E1 Gbit/s
C-F10 Gbit/s
C-D10 Gbit/s
D-E10 Gbit/s
D-G10 Gbit/s
E-G1 Gbit/s

Question 5

#

Calculer le coût pour chaque débit de la figure 2 avec coût = 10¹⁰/d, d en bit/s.

Indice

Convertissez tous les débits en bits par seconde avant la division.

Comprendre la correction
DébitEn bit/sCoût
10 Gbit/s10¹⁰1
1 Gbit/s10⁹10
100 Mbit/s10⁸100

Chaque division du débit par dix multiplie le coût par dix. La métrique est sans unité dans ce modèle.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)

Question 6

#

Donner un ordre possible des routeurs suivis depuis A pour joindre Application avec OSPF.

Indice

Le chemin le moins coûteux peut posséder un routeur intermédiaire supplémentaire.

Comprendre la correction

A → B → C → D, de coût 1 + 10 + 1 = 12, ou A → B → E → D, de même coût 12. Le lien direct B-D coûte 100 à lui seul : A-B-D coûterait 101. Ces deux routes ex æquo sont toutes deux optimales ; il n’y a pas de raison de refuser l’une des deux.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)

Question 7

#

Les données TCP sont-elles contenues dans un paquet IP ou l’inverse ?

Indice

La couche transport TCP est portée par la couche réseau IP.

Comprendre la correction

Le segment TCP est encapsulé dans le paquet IP, qui le transporte comme charge utile. Le segment TCP contient à son tour les données applicatives et son propre en-tête. Ce n’est pas le paquet IP qui est à l’intérieur d’un segment TCP.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)

Question 8

#

Pourquoi une file est-elle préférable à une pile pour les paquets à transmettre ?

Indice

Comparez le sort d’un ancien paquet lorsque de nouveaux paquets arrivent constamment.

Comprendre la correction

Une file applique FIFO : le premier paquet arrivé attend le moins et est transmis avant les suivants. Cela conserve l’ordre d’arrivée au niveau de cette file et évite qu’un ancien paquet reste toujours derrière les nouveaux. Une pile LIFO favoriserait les derniers reçus et pourrait retarder indéfiniment les plus anciens si les arrivées continuent. Cela ne signifie pas que tout le réseau garantit un ordre de livraison de bout en bout.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)

Interface des files : cree_file() crée une file vide ; est_vide(f) teste son état ; enfile(f,x) ajoute ; defile(f) retire et renvoie le plus ancien élément d’une file non vide. Exemple : enfiler 0 puis 1, puis défiler, renvoie 0 et laisse 1. En drop tail, les nouveaux paquets sont ignorés quand la file atteint t_max ; un paquet transmis est défilé.

class Routeur_DROP_TAIL:
    def __init__(..., ...):
        self.f = ...
        self.t_max = ...
        self.t = ...

Question 9

#

Compléter le constructeur de Routeur_DROP_TAIL.

Indice

Une capacité n’est pas la taille actuelle de la file.

Comprendre la correction
def __init__(self, t_max):
    self.f = cree_file()
    self.t_max = t_max
    self.t = 0

La file est vide, donc sa taille courante vaut 0. La capacité maximale est reçue en paramètre et conservée. L’invariant à maintenir ensuite est self.t égal au nombre de paquets réellement dans self.f. Le compteur doit donc augmenter à chaque enfile et diminuer à chaque transmission avec defile.

Voir la question dans le sujet PDF, p. 10 (nouvel onglet)
def recoit(self, p):
    if ...:
        enfile(..., ...)
        self.t = ...
        return ...
    return ...

Question 10

#

Compléter recoit(p), qui accepte et enfile le paquet seulement si la file n’est pas pleine.

Indice

Les mutations doivent rester dans la branche d’acceptation.

Comprendre la correction
def recoit(self, p):
    if self.t < self.t_max:
        enfile(self.f, p)
        self.t = self.t + 1
        return True
    return False

L’inégalité est stricte : avec t = t_max - 1, le dernier emplacement est accepté et le compteur devient t_max. À t = t_max, le paquet est refusé et rien ne change, ni la file ni son compteur. Renvoyer True sans enfiler ou augmenter t lors d’un refus romprait le lien entre état logique et contenu.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)

Drop tail peut synchroniser les ralentissements des émetteurs. La variante utilise f vide initialement, t=0, t_min et t_max positifs. Organigramme : ttirage_au_sort() renvoie un booléen aléatoire.

def recoit(self, p):
    if ... < ...:
        enfile(self.f, p)
        self.t = ...
        return ...
    elif ... <= ... < ...:
        if self.tirage_au_sort():
            enfile(self.f, p)
            self.t = ...
            return ...
    return ...

Question 11

#

Compléter recoit de Routeur_ALEA selon les trois zones de taille.

Indice

Découpez l’axe des tailles avec les bornes exactes : < t_min, t_min ≤ t < t_max, puis ≥ t_max.

Comprendre la correction
def recoit(self, p):
    if self.t < self.t_min:
        enfile(self.f, p)
        self.t = self.t + 1
        return True
    elif self.t_min <= self.t < self.t_max:
        if self.tirage_au_sort():
            enfile(self.f, p)
            self.t = self.t + 1
            return True
    return False

Sous t_min, l’acceptation est systématique. Entre les deux seuils, True au tirage signifie accepter dans le squelette donné ; False signifie refuser. À partir de t_max, aucun tirage ne doit pouvoir accepter un paquet. Le return False final couvre le tirage défavorable et la saturation. Les égalités sont importantes : t = t_min appartient à la zone aléatoire et t = t_max à la zone refusée.

Le sujet ne donne pas la loi du tirage ; on ne peut donc pas annoncer une probabilité numérique de refus. La méthode illustre une réaction avant saturation afin d’éviter que tous les émetteurs TCP ralentissent simultanément après une perte massive.

Voir la question dans le sujet PDF, p. 12 (nouvel onglet)
Admettre ou rejeter le prochain paquet ?Un atelier pour expérimenter

Faites varier la taille actuelle autour des seuils 3 et 6. Comparez drop tail et la variante aléatoire ; vous fixez le résultat du tirage pour explorer précisément les deux issues.

Lire le résultat de l’expérience initiale

Paquet accepté

Zone intermédiaire. Le tirage n’intervient que pour les tailles 3, 4 ou 5.

AvantConditionDécisionAprès
3Zone intermédiaireEnfiler et incrémenter4

Une politique de file se vérifie particulièrement aux seuils : juste avant, exactement dessus et juste après.

Revoir les notions de cet exercice

Exercice 3 · 8 points

Immeubles : SQL et plus longue sous-séquence croissante

Les deux parties sont indépendantes. La première gère les immeubles d’une agence avec deux tables ; la seconde modélise une rue perpendiculaire à la mer par une liste de hauteurs. Le schéma de la figure 1 est :

RelationAttributsClé primaireClé étrangère
immeubleid_immeuble ; nb_etage_immeuble ; numero_immeuble ; rue_immeubleid_immeuble-
appartementid_appart ; etage_appart ; prix_appart ; id_immeubleid_appartid_immeuble → immeuble.id_immeuble

Les attributs indiquent l’identifiant, le nombre d’étages, le numéro dans la rue et son nom pour un immeuble ; l’identifiant, l’étage, le prix et l’immeuble d’appartenance pour un appartement. On peut employer SELECT, FROM, WHERE, AND, OR, JOIN ... ON, INSERT, UPDATE, DELETE, DISTINCT et ORDER BY. « Appartement 603 » et « immeuble 16 » désignent leurs identifiants.

Question 1

#

Pourquoi numero_immeuble n’est-il pas la clé primaire ?

Indice

Une clé doit distinguer les immeubles de toutes les rues.

Comprendre la correction

Le même numéro existe dans plusieurs rues : un numéro 13 rue Turing et un numéro 13 rue de la mer sont deux immeubles distincts. Le numéro seul ne garantit donc pas l’unicité. id_immeuble fournit un identifiant indépendant de l’adresse.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

Question 2

#

Lister les identifiants des immeubles de la rue la mer en ordre croissant.

Indice

La colonne filtrée et la colonne triée ne sont pas nécessairement la même.

Comprendre la correction
SELECT id_immeuble
FROM immeuble
WHERE rue_immeuble = 'la mer'
ORDER BY id_immeuble ASC;

Le tri demandé porte sur l’identifiant, pas sur le numéro dans la rue ni sur la hauteur. ASC est facultatif car c’est l’ordre par défaut, mais rend le choix explicite.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

Question 3

#

Lister les identifiants des appartements de l’immeuble 16 situés au moins au 5e étage.

SQLSélectionner les appartements à partir du cinquième étageÉcrivez votre solution et mettez-la à l’épreuve

Listez les identifiants des appartements de l’immeuble 16 situés au moins au 5e étage. Le cinquième étage est inclus.

SELECT ...
FROM ...
WHERE ...;

Les cas de test proposés :

  • Jeu pédagogique : autour du seuil : Le schéma et l’immeuble 16 viennent du sujet ; aucune ligne d’appartement n’y est fournie à cette question. Ces valeurs sont des données complémentaires pédagogiques, avec un appartement juste avant la borne.
  • Cas complémentaire : le seul candidat est au 5 e : Jeu complémentaire pédagogique, distinct des données officielles. L’identifiant d’un appartement ne doit pas être confondu avec celui de son immeuble. Le test strict >5 ferait perdre le seul résultat attendu.
Indice

Utilisez >= pour inclure exactement 5.

Comprendre la correction
SELECT id_appart
FROM appartement
WHERE id_immeuble = 16 AND etage_appart >= 5;

Les deux contraintes doivent être satisfaites simultanément. « Au moins » inclut le cinquième étage. La table appartement contient déjà toutes les informations nécessaires, donc une jointure n’est pas requise.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

Question 4

#

Pourquoi DELETE FROM immeuble WHERE id_immeuble = 16 risque-t-il de rompre l’intégrité ?

Indice

Le lien de clé étrangère va de appartement vers immeuble.

Comprendre la correction

Des appartements peuvent encore référencer l’immeuble 16. Les supprimer de la table parent sans traiter ces références laisserait des clés étrangères orphelines. Un SGBD qui contrôle cette contrainte refuse normalement l’opération, sauf cascade explicitement configurée. Pour effacer les données d’un immeuble détruit, il faut traiter d’abord les appartements associés puis l’immeuble, dans une transaction si possible.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

Question 5

#

Ajouter l’immeuble 140, de six étages, au numéro 13 rue Turing.

Indice

L’identifiant et le numéro dans la rue sont deux informations distinctes.

Comprendre la correction
INSERT INTO immeuble
(id_immeuble, nb_etage_immeuble, numero_immeuble, rue_immeuble)
VALUES (140, 6, 13, 'Turing');

L’ordre explicite des colonnes évite de confondre identifiant 140 et numéro de rue 13. La clé 140 est supposée disponible pour cette nouvelle construction.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

Question 6

#

Le prix de l’appartement 603 double grâce à sa nouvelle vue sur mer. Le mettre à jour.

Indice

Le membre droit du SET peut employer la valeur actuelle de la colonne.

Comprendre la correction
UPDATE appartement
SET prix_appart = 2 * prix_appart
WHERE id_appart = 603;

La nouvelle valeur dépend de l’ancienne, qu’il n’est pas nécessaire de connaître dans le code de la requête. La condition cible un seul appartement ; elle ne modifie pas les autres prix de l’immeuble.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

MAX renvoie le maximum d’un attribut. Exemple fourni :

SELECT MAX(nb_etage_immeuble) FROM immeuble
WHERE id_immeuble <= 23;

Question 7

#

Obtenir le prix maximal d’un appartement situé dans un immeuble de la rue la mer.

Indice

Reliez le prix à la rue par id_immeuble.

Comprendre la correction
SELECT MAX(a.prix_appart)
FROM appartement AS a
JOIN immeuble AS i ON a.id_immeuble = i.id_immeuble
WHERE i.rue_immeuble = 'la mer';

Le prix appartient à appartement, la rue à immeuble. La jointure sélectionne les bonnes lignes avant MAX. La requête renvoie un prix unique, pas l’identifiant de l’appartement qui l’atteint ; plusieurs appartements peuvent avoir ce même prix. S’il n’y a aucun appartement correspondant, MAX renvoie NULL.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

Partie B : garder le maximum d’immeubles avec vue

On parcourt une rue depuis la mer. Un immeuble voit la mer si tous ceux placés avant lui ont moins d’étages. Détruire le moins d’immeubles revient à garder une sous-séquence strictement croissante de longueur maximale. Une sous-séquence supprime certains éléments sans déplacer les autres. Pour L1=[10,22,9,33,21,50,41,60], [22,9,50], [10,22] et [33] sont des sous-séquences ; [10] est croissante de longueur 1, [9,33] de longueur 2 et [10,22,33,50,60] est maximale de longueur 5. Pour la suite, L2=[3,1,8,2,5].

Question 8

#

Donner toutes les sous-séquences strictement croissantes de longueur 2 de L2 = [3,1,8,2,5].

Indice

Pour chaque paire d’indices i<j, testez L2[i]<L2[j].

Comprendre la correction

[3,8], [3,5], [1,8], [1,2], [1,5], [2,5]. Pour chaque premier élément, on cherche seulement à sa droite une valeur strictement plus grande. [2,8] n’est pas une sous-séquence, car 8 apparaît avant 2 ; [1,3] ne respecte pas non plus l’ordre original.

Voir la question dans le sujet PDF, p. 15 (nouvel onglet)

Question 9

#

Déterminer la plus longue sous-séquence strictement croissante de L2.

Indice

Le plus grand nombre de la liste n’appartient pas nécessairement à la meilleure sous-séquence.

Comprendre la correction
[1, 2, 5]

Elle a longueur 3. Les longueurs optimales terminant à chaque position sont [1,1,2,2,3]. Aucun élément ne permet de prolonger une chaîne jusqu’à 4 : 8 ne peut être suivi ni de 2 ni de 5, et le 3 initial ne peut pas précéder le 1 ou le 2 dans une suite croissante. Garder ces trois immeubles impose donc d’en retirer deux.

Voir la question dans le sujet PDF, p. 15 (nouvel onglet)

Question 10

#

Écrire est_strict_croissante(seq), qui renvoie un booléen.

Indice

La négation de « strictement plus grand » est « inférieur ou égal ».

Comprendre la correction
def est_strict_croissante(seq):
    for i in range(1, len(seq)):
        if seq[i] <= seq[i - 1]:
            return False
    return True

Une violation locale suffit : une égalité doit être refusée autant qu’une diminution. Si toutes les paires consécutives croissent, la transitivité garantit la croissance de toute la liste. Les listes vide ou d’un élément satisfont cette propriété sans comparaison. Placer return True dans la boucle arrêterait le contrôle après la première paire seulement.

Voir la question dans le sujet PDF, p. 15 (nouvel onglet)
def llsc_fin(tab, i):
    if ...:
        return ...
    max_len = 1
    for j in range(i):
        if tab[j] < ...:
            max_len = max(max_len, llsc_fin(tab, j) + 1)
    return max_len

def llsc_rec(tab):
    n = len(tab)
    return max([llsc_fin(tab, i) for i in range(n)])

Question 11

#

Compléter llsc_fin(tab,i), longueur maximale d’une sous-séquence croissante se terminant exactement à l’indice i.

Indice

L’état « finit en i » est plus précis que « meilleure chaîne parmi les i premières valeurs ».

Comprendre la correction
def llsc_fin(tab, i):
    if i == 0:
        return 1
    max_len = 1
    for j in range(i):
        if tab[j] < tab[i]:
            max_len = max(max_len, llsc_fin(tab, j) + 1)
    return max_len

La sous-séquence réduite à tab[i] justifie l’initialisation à 1. Chaque prédécesseur admissible j est placé avant i et porte une valeur plus petite ; on prolonge sa meilleure chaîne avec tab[i]. Le cas i=0 n’a aucun prédécesseur possible. La fonction globale prend le maximum sur toutes les positions finales : une meilleure chaîne ne se termine pas nécessairement au dernier élément. Le code fourni suppose tab non vide, car max([]) n’est pas défini.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)

Principe : dyn[i] contient la longueur maximale d’une sous-séquence croissante terminée à i. On parcourt tous les j

def llsc_dyn(tab):
    n = len(tab)
    dyn = [1] * n
    for i in range(1, n):
        for j in range(i):
            if tab[j] < tab[i]:
                dyn[i] = max(..., ...)
    return ...

Question 12

#

Compléter llsc_dyn aux lignes 7 et 8.

PythonTrouver une sous-séquence croissante, même avec des sautsÉcrivez votre solution et mettez-la à l’épreuve

Écrivez llsc_dyn(tab) pour une liste non vide de nombres. Renvoyez la longueur de sa plus longue sous-séquence strictement croissante : les éléments gardent leur ordre, mais ne sont pas obligatoirement consécutifs. dyn[i] mémorise la meilleure longueur qui se termine exactement à i. Le résultat est une longueur, pas une liste d’éléments.

def llsc_dyn(tab):
    # À vous de jouer
    pass

Les cas de test proposés :

  • Un élément suffit : Toute valeur isolée est une sous-séquence strictement croissante de longueur 1.
  • Ignorer des éléments intermédiaires : On peut conserver 1,2,4,6 ou 1,2,5,6 ; les positions n’ont pas à être contiguës.
  • Les égalités ne prolongent pas : Strictement croissante impose < et exclut <=.
  • La meilleure réponse ne finit pas à la dernière case : Renvoyer dyn[-1] donnerait 1, alors que le meilleur sous-problème vaut 4.
  • Des nombres négatifs et aucune mutation : Les comparaisons, pas le signe, définissent la croissance. Trier l’entrée modifierait les sous-séquences autorisées.
Indice

dyn[j] + 1 décrit une chaîne terminant par tab[j], prolongée avec tab[i].

Comprendre la correction
def llsc_dyn(tab):
    n = len(tab)
    dyn = [1] * n
    for i in range(1, n):
        for j in range(i):
            if tab[j] < tab[i]:
                dyn[i] = max(dyn[i], dyn[j] + 1)
    return max(dyn)

Les cases antérieures de dyn sont déjà terminées lorsque l’on calcule dyn[i]. Le maximum conserve la meilleure des chaînes qui peuvent précéder tab[i]. Pour L2, dyn devient [1,1,2,2,3]. On renvoie le maximum de toute la table, pas seulement dyn[-1]. Pour accepter aussi une liste vide, ajouter au début if not tab: return 0 ; cette extension évite max([]) sans changer les résultats demandés.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)

Question 13

#

Donner un avantage de cette implémentation par rapport à la version récursive.

Indice

Comparez les états déjà calculés et le coût de la pile, pas seulement la forme des boucles.

Comprendre la correction

Chaque état dyn[i] est calculé une seule fois et conservé. Les deux boucles font n(n-1)/2 comparaisons, donc O(n²) temps et O(n) mémoire pour la table. La version récursive sans cache peut recalculer les mêmes états très souvent, jusqu’à un nombre exponentiel d’appels sur une liste croissante. L’itération évite aussi une pile d’appels de profondeur linéaire et sa limite dans Python. Ce sont deux avantages distincts : partage des calculs et absence de récursion.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)
Quels immeubles garder pour laisser voir la mer ?Un atelier pour expérimenter

Choisissez une rue puis faites avancer le calcul. Chaque immeuble affiche la meilleure longueur de chaîne qui se termine à sa position ; le tableau ne calcule que les immeubles déjà traités.

Lire le résultat de l’expérience initiale

Chaîne conservée sur le préfixe : 1 < 2 < 5

Une hauteur égale ne suffit pas : le prochain immeuble doit dépasser strictement tous ceux conservés avant lui. Le résultat porte ici seulement sur le préfixe traité.

Indice depuis la merHauteurMeilleure longueur terminant iciPrédécesseur choisi
031Aucun
111Aucun
2820
3221
4533

Le bon état dynamique décrit la meilleure solution avec une fin imposée ; le maximum global se choisit ensuite parmi toutes ces fins.

Revoir les notions de cet exercice

Du sujet à la méthode

Votre prochaine séance de révision

  • Avant de compléter min-max, annoncez qui doit jouer dans l’état reçu par chaque appel.
  • Pour les files, testez toujours les valeurs exactement égales aux seuils.
  • En programmation dynamique, nommez précisément le sens d’une case avant d’écrire la transition.

Retrouver ces notions dans d’autres sujets

Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027

Énoncé : sujet 26-NSIJ1AN1 (PDF). Corrigé et explications pédagogiques proposés par Sofien.