X/ENS Informatique B MP-PC-PSI 2025

Thème de l'épreuve Le jeu de Röckse
Principaux outils utilisés listes, dictionnaires, recherche exhaustive, algorithme glouton, programmation dynamique, représentation binaire
Mots clefs jeu, algorithmique, glouton, binaire

Corrigé

 :
👈 gratuite pour tous les corrigés si tu crées un compte
👈 l'accès aux indications de tous les corrigés ne coûte que 1 € ⬅ clique ici
👈 gratuite pour tous les corrigés si tu crées un compte
- - - - - - - - - - -
👈 gratuite pour ce corrigé si tu crées un compte
- - -

Énoncé complet

(télécharger le PDF)
                       

Énoncé obtenu par reconnaissance optique des caractères


ECOLE POLYTECHNIQUE - ESPCI
ECOLES NORMALES SUPERIEURES

CONCOURS D'ADMISSION 2025

JEUDI 17 AVRIL 2025
16h30 - 18h30
FILIERES MP-PC-PSI
Epreuve n° 8

INFORMATIQUE B (XELSR)

Durée : 2 heures
L'utilisation des calculatrices n'est pas
autorisée pour cette épreuve

Le jeu de Rockse
On dispose sur les cases d'une grille N  N des penalites et des gains comptes 
comme
des penalites negatives. Le jeu de Rockse debute a la case (0, 0) et cherche un 
chemin vers la
case (N  1, N  1) qui minimise les penalites. A chaque etape du chemin, un 
nombre fini
de deplacements (sauts) est autorise. Des cases bonus ajoutent, une fois 
atteintes, des sauts
possibles pour la suite du chemin. La figure ci-dessous donne un exemple de 
grille et deux
chemins possibles, en supposant que les sauts autorises sur la grille sont (i, 
j)  (i, j + 1) (),
(i, j)  (i + 1, j  1) () et (i, j)  (i + 1, j + 1) (). On suppose par ailleurs 
une case bonus
en (1, 2) (grisee) qui, une fois atteinte, ajoute aux sauts autorises (i, j)  
(i + 1, j) () :

0
0

1

2

3

DEPART

2

4

6

1

1

2

2

2

2

2

3

3

1

4

3

()

0

0

0

3

1

4

2

7

3

ARRIVEE

(a) Grille de jeu

1

2

3

DEPART

2  4  6
0

()
2
1
2
3

2
3
2
4

1
3
4
7

ARRIVEE

0
0
1
2
3

(b) Chemin non-optimal
poids : 6

1

2

3

DEPART

2  4  6
0

()
2  2
1
3

2
3
2
4

1
3  7
4

ARRIVEE

(c) Chemin optimal
poids : 7

Considerons le chemin decrit en (c). On part de la case de depart (0, 0) et, en 
appliquant les
sauts successifs , , , , on arrive sur la case bonus. A partir de cette case, 
le saut  est
desormais autorise. On applique ensuite les sauts , ,  pour atteindre la case 
d'arrivee (3, 3).
Le chemin obtenu est decrit par la liste des cases :
chemin = [(0 ,0) ,(0 ,1) ,(0 ,2) ,(1 ,1) ,(1 ,2) ,(2 ,2) ,(3 ,2) ,(3 ,3)]

Son poids est la somme des penalites contenues dans ces cases, soit 7. On peut 
montrer que
c'est un chemin de poids minimal -- on dit qu'il est optimal. Le chemin decrit 
en (b) est correct,
mais son poids est de 6. Il n'est donc pas optimal.
Representation des donnees. La grille de jeu N  N est representee par une liste 
de listes
d'entiers T telle que T[i][j] est la penalite a la case (i, j). On suppose que 
cette representation
est bien formee, c'est-a-dire que toutes les listes ont la meme taille N . Sur 
notre exemple :
T = [[2 , -4 , -6 ,0] ,[1 , -2 ,2 ,3] ,[ -2 ,2 , -3 ,4] ,[ -1 ,4 , -3 ,7]]

Un saut (i, j)  (i + i, j + j) est represente par le couple d'entiers (i, j). 
L'ensemble des
sauts possibles est ainsi une liste de couples. Sur notre exemple, l'ensemble 
des sauts possibles
par defaut (avant d'avoir atteint une case bonus) est :
sauts = [(0 ,1) ,(1 , -1) ,(1 ,1)]

Les sauts actives par les cases bonus sont enregistres dans un dictionnaire 
bonus tel que
bonus[(i,j)] est la liste des sauts actives par la case bonus (i,j). Sur notre 
exemple :
bonus = { (1 ,2): [(1 ,0)] }

1

Complexite. La complexite d'une fonction F est le nombre d'operations 
elementaires (addition, multiplication, a!ectation, test, etc.) necessaires a 
l'execution de F . Lorsque cette complexite depend de plusieurs parametres n et 
m, on dira que F a une complexite en O((n, m))
lorsqu'il existe trois constantes A, n0 et m0 telles que la complexite de F est 
inferieure ou egale
a A · (n, m), pour tout n  n0 et m  m0 . Lorsqu'il est demande de donner la 
complexite d'un
programme, le candidat devra justifier sa reponse en utilisant le code du 
programme.
Rappels sur Python. L'utilisation de toute fonction Python sur les listes ou 
sur les dictionnaires autre que celles mentionnees dans ce paragraphe est 
interdite. Sur les listes, on autorise
les operations suivantes, dont la complexite est en O(1) :
-- len(l) renvoie la longueur de la liste l.
-- l[i] designe l'element d'indice i de la liste l, pour 0  i < len(l). -- l.append(e) ajoute en place l'element e a la fin de la liste l. Les operations suivantes sont egalement autorisees et leur complexite est en O(n) : -- l1 + l2 renvoie une nouvelle liste (de longueur n) qui est la concatenation des listes l1 et l2. -- range(n) renvoie la liste [0,1, ..., n-1]. -- range(n-1,-1,-1) renvoie la liste [n-1, ..., 0]. -- l.pop(0) retire le premier element e de la liste l (de longueur n) et renvoie e. -- (e in l) renvoie True si l'element e est dans la liste l (de longueur n), et False sinon. -- l[:] renvoie une copie de la liste l (de longueur n). On autorise egalement les constructions suivantes : -- La construction for e in l parcourt (itere sur) les elements de la liste l du premier element (d'indice 0), au dernier element (d'indice len(l)-1) avec la complexite O(len(l)). -- La construction [f(e) for e in l] produit la meme liste que le code suivant, et avec la complexite len(l) fois la complexite de f : result = [] for e in l : result . append ( f ( e )) return result Sur les dictionnaires, on autorise uniquement les operations suivantes, dont la complexite est en O(1) : -- Le test (e in d) renvoie True si e est une cle du dictionnaire d, et False sinon. -- L'acces d[e] a l'element associe a la cle e dans le dictionnaire d. On autorise egalement les constructions suivantes sur les dictionnaires : -- La construction for (k,v) in d parcourt les elements du dictionnaire d. -- La construction d[k] = v a!ecte la valeur v a la cle k. Enfin, on supposera donnee une variable globale INFINI qui contient un entier superieur ou egal a tout entier utilise dans les programmes de ce sujet. Organisation. Les parties peuvent etre traitees independamment. La partie I porte sur les fonctions de base sur les chemins et les sauts. Ensuite, la partie II propose de trouver un chemin optimal avec une recherche exhaustive. La partie III utilise les resultats de la partie II pour construire une methode de recherche gloutonne. Enfin, la partie IV etudie une resolution du probleme par programmation dynamique. On rappelle que le code doit etre commente. 2 Partie I : Sauts et chemins Question 1 Ecrire une fonction poids(T,chemin) qui, etant donne un plateau de jeu T et un chemin chemin, renvoie le poids de ce chemin. Donner la complexite de cette fonction. Question 2 Ecrire une fonction appliquer sauts(i,j,sauts) qui, etant donne une case (i, j) et une liste de sauts sauts, applique les sauts dans l'ordre donne par la liste sauts, en partant de la case (i, j) et renvoie la case atteinte. On suppose que le chemin indique par sauts reste dans la grille. Question 3 Ecrire une fonction sauts corrects(sauts,bonus,chemin) qui, etant donne l'ensemble des sauts par defaut represente par la liste sauts, les sauts associes aux cases bonus bonus et un chemin chemin, renvoie True si les sauts utilises dans le chemin sont corrects et False sinon. On suppose que le chemin reste dans la grille. On dit qu'un ensemble de sauts C est bien forme s'il ne peut pas mener a un cycle. Autrement dit, s'il n'existe pas de suite de sauts construite a partir des sauts de C qui, en partant de la case (0, 0) permettrait d'arriver sur une case (i, j) puis de revenir a cette case. Une condition su!sante est que chaque saut (i, j) de l'ensemble de sauts C soit strictement positif lexicographiquement, condition notee (i, j)  0 et definie par : i > 0 ou bien (i = 0 et j > 0)

(*)

Une suite de sauts  est positive lexicographiquement, propriete notee   0, si 
chaque saut
(i, j) de  satisfait (*).
Question 4 Ecrire une fonction sauts bien formes(sauts,bonus) qui, etant donne 
la liste
des sauts par defaut sauts et les sauts associes aux cases bonus bonus, verifie 
que chaque saut
de ces listes satisfait la condition (*). La fonction renvoie True si c'est le 
cas et False sinon.
Dans le reste du sujet, on suppose que les sauts utilises satisfont la 
condition (*).

Partie II : Recherche exhaustive
On cherche maintenant a calculer un chemin optimal. Une premiere solution est 
de tester
tous les chemins corrects et de retenir le chemin de poids minimum. 
L'enumeration de tous les
chemins corrects se fera avec la fonction auxiliaire recursive suivante :
trouve_complet_rec(T,sauts,bonus,sauts_max,i,j)
Etant donnes une grille de jeu T, les sauts par defaut sauts, les sauts 
associes aux cases
bonus bonus, une valeur entiere sauts max et une case (i, j), la fonction 
ci-dessus calcule un
chemin p de poids minimum partant de (i, j) et n'arrivant pas forcement a (N  
1, N  1),
mais ayant au plus sauts max+1 cases. La fonction renvoie le couple (poids 
min,sauts min)
ou poids min est le poids de p et sauts min est la liste des sauts pour 
construire p.
La valeur sauts max est utilisee pour limiter la complexite de la recherche. 
Ainsi, la longueur
du resultat sauts min sera inferieure ou egale a sauts max. Cette limite est 
appelee horizon. Si
l'horizon est assez grand, la fonction renvoie un chemin optimal partant de (i, 
j).
Question 5 Quelle est la longueur maximale L d'un chemin de (0, 0) a (N  1, N  
1) pour
n'importe quel ensemble de sauts satisfaisant (*) ? Donner un exemple 
d'ensemble de sauts par
defaut pour lequel se realise ce chemin de longueur L.
3

Question 6 Ecrire la fonction auxiliaire trouve complet rec(T,sauts,bonus,sauts 
max,i,j)
et la fonction principale trouve complet(T,sauts,bonus,sauts max) qui renvoie 
le couple
(poids,sauts) ou poids est le poids du chemin trouve et sauts est la liste des 
sauts du chemin
trouve. Quelle est la complexite de cette fonction ?
Question 7 La fonction trouve complet rec perd beaucoup de temps a refaire les 
memes calculs. On peut l'ameliorer en enregistrant les resultats deja calcules 
pour une valeur fixee de
sauts max. Expliquer en quelques lignes comment proceder. Quelle serait alors 
la complexite ?
La recherche exhaustive d'un chemin optimal est obtenue en appelant trouve 
complet(T,
sauts, bonus, L) avec L la longueur maximale d'un chemin dans T.

Partie III : Recherche gloutonne
On peut reduire la complexite de la recherche exhaustive en limitant, a chaque 
etape, l'horizon de la recherche, c'est-a-dire le nombre de sauts regardes a 
partir de la case courante. D'ou
l'idee de construire un algorithme glouton pour trouver une solution. En 
partant de la case (0, 0),
on utilise la recherche exhaustive avec un  petit horizon  k pour determiner la 
meilleure suite
locale de k sauts, c'est-a-dire la suite de poids minimal et d'au plus k sauts. 
On joue les sauts
de la meilleure suite locale, puis on recommence sur la case atteinte, jusqu'a 
la case d'arrivee
(N  1, N  1). Si la recherche exhaustive avec l'horizon k ne trouve pas une 
suite locale, alors
l'algorithme abandonne la recherche.
Question 8 Sur l'exemple donne en introduction, quel est le chemin trouve pour 
k = 2 ? Est-il
optimal ? Memes questions pour k = 3. En augmentant k, obtient-on forcement un 
chemin de
poids plus petit ?
Question 9 Ecrire une fonction trouve glouton(T,sauts,bonus,k) qui, etant 
donnes la
grille de jeu T, la liste des sauts par defaut sauts, les sauts associes aux 
cases bonus bonus
et l'horizon k, e"ectue cette recherche gloutonne et renvoie le couple 
(poids,sauts) ou poids
est le poids du chemin trouve et sauts est la liste des sauts du chemin trouve. 
Quand la recherche
exhaustive avec l'horizon k ne trouve pas de suite locale, la fonction renvoie 
(INFINI,[]).

Partie IV : Recherche par programmation dynamique
On se propose maintenant de construire une methode par programmation dynamique 
pour
trouver un chemin optimal. Comme la solution optimale en partant de (i, j) 
depend des cases
bonus deja rencontrees (dites activees), on va remplir un tableau poids 
opt[i][j][code bonus]
qui contient le poids du chemin optimal en partant de la case (i,j) et ou la 3e 
dimension
(code bonus) encode l'ensemble des cases bonus activees. En parallele, on 
remplira un tableau
saut opt[i][j][code bonus] qui contient un saut optimal a jouer en partant de 
la case (i,j).
Ce tableau permettra de retrouver un chemin optimal.

1) Encodage des cases bonus activees
Soit n le nombre de cases bonus. On numerote les cases bonus de 0 a n  1. On 
represente les
cases bonus activees par une liste de booleens (masque binaire) [b0 , . . . , 
bn1 ] ou bk vaut True si
et seulement si la case bonus k est activee. L'ensemble des cases bonus 
activees est encode par
^ = 1 et False
^ = 0. Ce code est note
l'entier dont la representation binaire est bn1 . . . b0 , ou True
b0 . . . bn1 . Par exemple, le code associe au masque [False, False] est 0, le 
code associe au
masque [True, False] est 1, etc.
4

Question 10 Ecrire la fonction code bonus(masque bonus) qui renvoie le code 
associe au
masque binaire masque bonus representant l'ensemble des cases bonus activees.

2) Recurrence
On
va
maintenant
donner
une
recurrence
pour
calculer
les
valeurs
poids opt[i][j][code bonus] pour tout case (i, j) et valeur de code bonus. 
Cette recurrence
sera utilisee dans la section suivante pour construire un algorithme.
Pour simplifier l'explication, ignorons les cases bonus dans un premier temps. 
Si un chemin
partant de la case (i, j) a un poids minimal, alors le chemin obtenu en lui 
retirant (i, j) (en
partant de la case suivante) a lui-meme un poids minimal. Une fois poids opt 
calcule pour tous
les successeurs possibles de la case (i, j), il su"t de garder le plus petit et 
de lui ajouter le poids
de la case T[i][j]. On obtient ainsi poids opt pour la case (i, j).
Avec les cases bonus, c'est le meme principe sauf qu'il faut gerer les sauts 
supplementaires
des cases bonus activees. Deux cas sont possibles :
-- (i, j) n'est pas une case bonus. Dans ce cas, il su"t de calculer
poids opt[i s][j s][code bonus] pour tous les successeurs possibles (i s,j s)
de la case (i, j) avec les sauts par defaut et les sauts associes a chaque case 
bonus
activee de code bonus. Le poids opt[i][j][code bonus] est alors le minimum de 
ces
poids opt[i s][j s][code bonus] auquel s'ajoute la penalite T[i][j] de la case 
(i, j).
-- (i, j) est une case bonus. Dans ce cas, c'est le meme processus, sauf que : 
1) parmi les
sauts possibles, on ajoute ceux actives par la case bonus et 2) on considere 
les successeurs
(i s,j s) avec un nouveau code bonus code bonus' dans lequel la case bonus (i, 
j) est
activee : le minimum doit ainsi etre calcule parmi les poids opt[i s][j s][code 
bonus'].
Si code bonus = b0 . . . bn1 , en notant k le numero de la case bonus (i, j), 
on changera
bk a True pour obtenir code bonus' = b0 . . . bk . . . bn1 , ou bk = True.
Question 11 Ecrire la definition de poids opt[i][j][b0 . . . bn1 ]. On notera # 
l'ensemble
des sauts par defaut et $k l'ensemble des sauts associe a la k-ieme case bonus.

3) Algorithme
On evalue iterativement les di!erentes cases poids opt[i][j][code bonus] de 
poids opt
a l'aide de trois boucles imbriquees iterant respectivement sur i, j et le 
masque de bonus (d'ou
on tirera code bonus). La di"culte est de trouver un ordre d'evaluation 
correct. A partir de la
recurrence, on voit que poids opt[i][j][b0 . . . bn1 ] doit etre evalue apres 
avoir obtenu les
poids des successeurs : poids opt[i+i][j+j][b0 . . . bn1 ].
-- Comme (i, j)  0 (condition (*)), alors i + i > i ou bien i = 0 et j + j > j, 
donc les
iterations sur i et j doivent etre  a l'envers , de N  1 a 0.

-- Soit [b0 , . . . , bn1 ] est egal a [b0 , . . . , bn1 ], soit [b0 , . . . , 
bn1 ] est obtenu a partir de
[b0 , . . . , bn1 ] en mettant un bk a True. En d'autres termes, le choix de 
bonus represente par
[b0 , . . . , bn1 ] est inclus dans le choix de bonus represente par [b0 , . . 
. , bn1 ]. La boucle sur
les masques de bonus doit donc iterer par choix de bonus decroissant au sens de 
l'inclusion.
Avec 3 cases bonus, un ordre est le suivant :
[True,True,True], puis
[False,True,True], [True,False,True], [True,True,False], puis
[False,False,True], [False,True,False], [True,False,False], puis
[False,False,False]
5

Noter que les choix rassembles sur une ligne ne sont pas classables entre eux. 
Par contre, les
choix d'une ligne sont strictement superieurs au sens de l'inclusion a l'un des 
choix de la ligne
suivante.
Un algorithme pour calculer l'ordre d'evaluation des masques de bonus peut 
proceder comme
suit. On commence par le choix total, dans notre exemple [True,True,True]. On 
construit la
ligne suivante en inserant les choix obtenus en basculant une coordonnee True a 
False. Par
exemple, on insere [False,True,True], [True,False,True], [True,True,False]. On 
itere
ensuite sur chaque choix obtenu pour construire la ligne d'apres. On s'arrete 
lorsqu'on atteint le
choix vide, dans notre exemple [False,False,False]. On pourra utiliser une file 
pour defiler
les choix de bonus a traiter et enfiler progressivement les choix de bonus de 
la ligne suivante.
Question 12 Ecrire une fonction combinaisons bonus(nb bonus) qui, etant donne 
le nombre
total de cases bonus nb bonus, renvoie la liste de masques bonus ordonnee de 
maniere
decroissante dans le sens de l'inclusion, en codant l'algorithme ci-dessus.
On suppose qu'il existe une fonction ranger bonus(bonus) qui renvoie un couple
(bonus au rang,rang du bonus) tel que :
-- bonus au rang[k] est la liste des sauts actives par la case bonus dont le 
numero est k,
-- rang du bonus[(i,j)] est le numero de la case bonus (i, j) dans un masque de 
bonus.
Le resultat de cette fonction est utilise comme parametre pour la question 
suivante et pour la
fonction ajouter bonus decrite apres.
Question 13 Ecrire une fonction
trouver_sauts_possibles(sauts,bonus_au_rang,masque_bonus)
qui, etant donnes les sauts par defaut sauts, la liste bonus au rang renvoyee 
par
ranger bonus(bonus) et le masque des bonus actives masque bonus, renvoie 
l'ensemble des
sauts possibles.
On suppose donnee une fonction
ajouter_bonus(bonus,rang_du_bonus,i,j,bonus_actifs,code_bonus_actifs)
qui active la case bonus (i, j) dans le code des cases bonus activees code 
bonus actifs.
Si (i, j) n'est pas une case bonus, la fonction renvoie code bonus actifs. 
L'argument
rang du bonus est la structure renvoyee par ranger bonus(bonus) et l'argument 
bonus actifs
est le masque des bonus actifs dont le code est code bonus actifs.
Question 14 Le code Python incomplet de la page suivante implemente la fonction
trouve_dynamique(T,sauts,bonus) qui, etant donnes une grille de jeu T, la liste 
des sauts
par defaut sauts et les sauts associes aux cases bonus bonus, calcule le chemin 
optimal par
programmation dynamique en utilisant la recurrence trouvee en question 11. Le 
resultat de la
fonction est le couple (poids opt,sauts opt) ou poids opt est le poids du 
chemin optimal
trouve et sauts opt est la liste des sauts de ce chemin.
1. Donner les sept parties manquantes indiquees par  ...  dans le code.
2. Quelle est la complexite de cette fonction ?

6

def trouve_dynamique (T , sauts , bonus ):
N = len ( T )
nb_bonus = len ( bonus )
nb_code_bonus = ...
# A COMPLETER ( 1 )
poids_opt = [[[ INFINI for bonus_code in range ( nb_code_bonus )]
for j in range ( N )] for i in range ( N )]
saut_opt = [[[(0 ,0 ,0) for bonus_code in range ( nb_code_bonus )]
for j in range ( N )] for i in range ( N )]
( bonus_au_rang , rang_du_bonus ) = ranger_bonus ( bonus )
for bonus_actifs in combinaiso ns _b on us ( nb_bonus ):
code_bonus_actifs = code_bonus ( bonus_actifs )
poids_opt [N -1][ N -1][ code_bonus _actif s ] = ...
# A COMPLETER ( 2 )
sauts_possibles = ...
# A COMPLETER ( 3 )
for i in range (...):
# A COMPLETER ( 4 )
for j in range (...):
# A COMPLETER ( 5 )
code_bonus_dest = ajouter_bonus ( bonus , rang_du_bonus ,i ,j ,
bonus_actifs , co de_bon us_ac tifs )
if (i , j ) in bonus :
s a u t s _ p o s s i b l e s _ f i n a l = sauts_possibles + bonus [( i , j )]
else :
s a u t s _ p o s s i b l e s _ f i n a l = sauts_possibles
for ( delta_i , delta_j ) in s a u t s _ p o s s i b l e s _ f i n a l :
i_dest = i + delta_i
j_dest = j + delta_j
if ( i_dest in range ( N ) and j_dest in range ( N )):
poids_opt_dest = poids_opt [ i_dest ][ j_dest ][ code_bonus_dest ]
if ( poids_opt [ i ][ j ][ code_bonus_actifs ] > poids_opt_dest ):
poids_opt [ i ][ j ][ code_bonus_actifs ] = ... # A COMPLETER ( 6 )
saut_opt [ i ][ j ][ cod e_bonu s_acti fs ] = ... # A COMPLETER ( 7 )
poids_opt [ i ][ j ][ code_bonus _actif s ] += T [ i ][ j ]
return ( poids_opt , saut_opt )

Question 15 Ecrire la fonction solution dynamique(saut opt,N) qui, etant donnes 
la structure saut opt calculee dans la question precedente et la dimension N de 
la grille, renvoie le
chemin optimal correspondant. La fonction renvoie le chemin vide [] s'il 
n'existe pas de chemin
entre la case de depart et celle d'arrivee.
­

F

I

7

N

­