X ENS Informatique C MPI 2025Sujet, corrigé et rapport du jury
- Programmation OCaml : listes, types récursifs, arbres et forêts
- Graphes non orientés, couplages et chemins d'augmentation
- Complexité algorithmique
- Programmation C : tableaux, pointeurs, allocation dynamique
- Calcul de déterminants
- Probabilités et lemme de Schwartz-Zippel
Téléchargements
Présentation du sujet
DifficileCouplages maximaux et parfaits dans les graphes : algorithme d'Edmonds, calcul de déterminants et méthode probabilisteAfficher ou masquer la section
Présentation du sujet
DifficileLe sujet étudie les couplages dans les graphes non orientés. La partie I, en OCaml, porte sur l'algorithme d'Edmonds de recherche d'un couplage maximal par chemins d'augmentation et contraction de bourgeons. Les parties II et III, en langage C, portent sur l'existence d'un couplage parfait via le calcul du déterminant d'une matrice (algorithme de Mahajan-Vinay) puis un test probabiliste basé sur la matrice de Tutte et le lemme de Schwartz-Zippel.
- 1Partie I : Algorithme d'EdmondsProgrammer en OCaml la recherche d'un couplage maximal par chemins d'augmentation, avec contraction de bourgeons.
- 2Partie II : Calculs de déterminantsProgrammer en C l'algorithme de Mahajan-Vinay, qui calcule un déterminant à partir de suites de marches fermées dans un graphe pondéré.
- 3Partie III : Méthode algébrique et probabiliste pour les couplages parfaitsUtiliser la matrice de Tutte et le lemme de Schwartz-Zippel pour tester probabilistiquement l'existence d'un couplage parfait.
Difficile. La moyenne des 396 copies est de 9,38/20 et le taux de traitement des questions chute fortement en fin de sujet : 31 % pour la question 30, 22 % pour la question 31, et seulement 12 % pour la dernière question.
L'épreuve en chiffres
Moyenne 9,38 / 20 · écart-type 3,86 · 396 copies · où vous situez-vous ?Afficher ou masquer la section
L'épreuve en chiffres
- Moyenne
- 9,38/ 20
- Écart-type
- 3,86
- Copies
- 396
Votre note sur 20 à ce sujet, en conditions de concours.
Source : rapport du jury. Notes publiées par le concours (après harmonisation le cas échéant). Courbe : estimation par une loi normale.
Ce qu'a observé le jury
6 erreurs relevéesUtilisation de fonctions OCaml interdites · Copies mal rédigées ou peu lisibles · Raisonnement par l'absurde inutilement compliquéAfficher ou masquer la section
Ce qu'a observé le jury
6 erreurs relevéesLe jury insiste sur l'importance de lire le préambule, en particulier la liste des fonctions OCaml autorisées, et de rendre une copie propre et lisible. Les premières questions de chaque partie, plutôt des questions de compréhension, sont généralement bien traitées, tandis que le taux de réussite et de traitement baisse nettement sur les questions de code plus avancées de fin de partie.
Les erreurs les plus sanctionnées
- 1Utilisation de fonctions OCaml interdites
Le préambule listait les fonctions OCaml autorisées ; les autres, dont List.iter, étaient interdites, et leur usage a fait perdre des points.
« Nous avons retiré des points en cas d'utilisation de fonctions non autorisées. »
- 2Copies mal rédigées ou peu lisibles
Le jury rappelle que le correcteur ne doit pas avoir à faire d'effort pour lire une copie et qu'en cas de doute sur ce qui est écrit, les points ne sont pas distribués.
« La correctrice ou le correcteur ne doit pas avoir à faire d'effort pour lire votre copie. »
- 3Raisonnement par l'absurde inutilement compliquéQ8, Q12
Plusieurs copies utilisent un raisonnement par l'absurde là où une démonstration constructive aurait été plus simple à rédiger, par exemple aux questions 8 et 12.
« Les démonstrations par l'absurde ne sont pas forcément les plus simples »
- 4Poids additionnés au lieu d'être multipliésQ25
À la question 25 sur les marches fermées, plusieurs copies calculent mal les poids en les additionnant au lieu de les multiplier.
« Plusieurs copies ont mal calculé les poids, en les additionnant au lieu de les multiplier »
- 5Arêtes non ordonnéesQ9, Q13
Le sujet précisait qu'une arête devait être représentée par une paire ordonnée (min a b, max a b) ; l'oubli de cet ordre a été pénalisé aux questions 9 et 13.
« il fallait donc penser à ordonner les arêtes »
- 6Code peu commenté ou mal nommé
Le jury demande des noms de fonctions et de variables clairs et rappelle qu'un code de plus d'une page ne peut pas se passer de commentaires, surtout pour les fonctions auxiliaires anonymes.
« un code de plus d'une page ne peut pas se passer de commentaires »
Ce qui a été bien réussi
- La question 1, facile, a été bien traitée par la quasi-totalité des candidats (100 % de traitement, score moyen de 4,62/5).
- La question 5 a été traitée sans difficulté signalée (score moyen de 4,96/5).
- La question 7 a été très bien traitée par 99 % des candidats.
- Les questions 22 à 24, de compréhension simple sur les matrices linéarisées, ont été très bien réussies.
- La question 29, plutôt simple, a été plutôt bien traitée par les candidats qui l'ont abordée.
Conseils du jury
- Lire attentivement le préambule de l'énoncé, notamment la liste des fonctions autorisées.
- Préférer les démonstrations constructives aux raisonnements par l'absurde, plus faciles à rédiger.
- Choisir des noms de fonctions et de variables simples et éclairants, et commenter tout code de plus d'une page.
- Vérifier son code sur des exemples simples, notamment des listes à un ou deux éléments, pour tester les conditions d'arrêt des fonctions récursives.
Synthèse rédigée par WikiPrépa à partir du rapport officiel du jury (à télécharger en PDF). Les citations sont extraites du rapport.
Ces sujets peuvent vous intéresser
Lecture du sujet en ligne
L'énoncé complet, avec les formules et les figures, sans ouvrir le PDF.Afficher ou masquer la section
Lecture du sujet en ligne
ECOLE POLYTECHNIQUE ECOLES NORMALES SUPERIEURES
CONCOURS D'ADMISSION 2025
JEUDI 17 AVRIL 2025 08h00-12h00
FILIERE MPI - Epreuve
n^∘7
INFORMATIQUE C (XULSR)
Couplages maximaux et parfaits
- Option.get: 'a option -> 'a renvoie v si l'argument de type 'a option est égal à Some v , et lève une exception sinon. La complexité est en
O(1) . - List.length: 'a list -> int renvoie la taille de la liste donnée en argument. La complexité est en
O(n) , oùn est la longueur de la liste donnée en argument. - List.hd: 'a list -> 'a renvoie le premier élément de la liste donnée en argument si celle-ci n'est pas vide, et lève une exception sinon. La complexité est en
O(1) . - List.tl: 'a list -> 'a list renvoie la liste donnée en argument privée de son premier élément si la liste donnée en argument n'est pas vide, et lève une exception sinon. La complexité est en
O(1) . - List.rev: 'a list -> 'a list renvoie la liste donnée en argument dans l'ordre inverse. La complexité est en
O(n) , oùn est la longueur de la liste donnée en argument. - List.map: ('a -> 'b) -> 'a list -> 'b list renvoie la liste de type 'b list obtenue en appliquant la fonction de type ' a -> ' b donnée en premier argument à chaque élément de la liste de type 'a list donnée en second argument. Si la complexité de f est en
O(1) , alors la complexité de List.map f lst est enO(n) , oùn est la longueur de lst. - List.mem: 'a -> 'a list -> bool renvoie true si la valeur donnée en premier argument est un élément de la liste donnée en second argument, et renvoie false sinon. La complexité est en
O(n) , oùn est la longueur de la liste donnée en second argument. - List.assoc: 'a -> ('a * 'b) list -> 'b: cette fonction spécifique aux listes d'association est décrite en page 4.
- Un chemin est une suite finie de sommets
v_0, v_1, …, v_n avecn ⩾ 0 et telle que pour touti = 0, …, n − 1 , on a{v_i, v_(i + 1)} ∈ E . La longueur du cheminv_0, v_1, …, v_n estn . - Un chemin est élémentaire si ses sommets sont deux-à-deux distincts.
- Un cycle est un chemin
v_0, v_1, …, v_n de longueur⩾ 3 , avecv_0 = v_n et tel que le cheminv_1, …, v_n est élémentaire. Autrement dit, le seul sommet autorisé à apparaître plusieurs fois dans un cylcev_0, v_1, …, v_n est le sommetv_0 = v_n , qui apparaît exactement deux fois (une fois au début et une fois à la fin).
Partie I : Algorithme d'Edmonds
Question 1. Écrire une fonction del: 'a list
type graphe
Un graphe est représenté par une liste de paires (a,lst) où a est un sommet du graphe et lst est la liste d'adjacence de ce sommet. Dans la suite, on suppose toujours que :
- les sommets du graphe sont exactement les clefs de la liste d'association;
- chaque clé de la liste d'association est unique.
Couplages
.jpg)
.jpg)
.jpg)
.jpg)
.jpg)
(1)
(2)
(3)
type couplage
Dans toute la suite, une arête
Étant donné un couplage
On attend une complexité en
Couplages maximaux
.jpg)
Chemins d'augmentation
-
v_0 etv_n ne sont pas couverts parC ; - c'est un chemin alternant : pour tout
i = 0, …, n − 2 , on a{v_i, v_(i + 1)} ∈ C si et seulement si{v_(i + 1), v_(i + 2)} ∉ C .
Un chemin d'augmentationP pourC permet de construire un nouveau couplageC(P) constitué des arêtes deC qui ne sont pas dansP et des arêtes deP qui ne sont pas dansC .

le chemin suivant est un chemin d'augmentation :
.jpg)
pour le couplage

separer : int list -> ((int * int) list) * ((int * int) list)
qui prend en entrée un chemin d'augmentation chm et renvoie deux listes lstin et lstout où lstin contient les arêtes composant le chemin chm qui appartiennent au couplage, et où lstout contient les arêtes composant le chemin chm qui n'appartiennent pas au couplage.
On attend une complexité en
Question 10. Écrire une fonction augmente: couplage -> int list -> couplage qui prend en entrée un couplage cpl et un chemin d'augmentation chm, et qui renvoie le couplage cpl(chm).
Bourgeons
de

- L'ensemble des sommets du graphe
G/L est(V∖L) ∪ {w} oùw , le nouveau sommet deG/L , est un entier qui n'est pas un sommet deG . - Les arêtes de
G/L sont les arêtes deG entre sommets n'étant pas dansL , ainsi que les arêtes de la forme{u, w} oùu n'appartient pas àL mais est adjacent dansG à un sommet deL . L'ensemble des arêtes deG/L est donc défini comme
{{u, v} ∈ E|u ∉ L etv ∉ L} ∪ {{u, w}|u ∉ L etu adjacent dansG à un sommet deL}
- les arêtes
{a, b} ∈ C telles quea ∉ B etb ∉ B; - les arêtes
{a, w} telles quea ∉ B et telles qu'il existe unb ∈ B avec{a, b} ∈ C .
Question 13. Écrire une fonction
contracteC : couplage -> int list -> int -> couplage
qui prend en entrée un couplage cpl, un bourgeon brg, et le nom w du nouveau sommet, et qui renvoie le couplage
Recherche de chemins d'augmentation
type arbre = N of int * foret
and foret = arbre list
On attend une complexité en
Question 15. Écrire une fonction extend: foret
(1) On construit une forêt
(2) On fixe
(3) Tant qu'il existe un sommet
Pour chaque voisin
(a) Si
(b) Si
(i) Si
(ii) Si
- La fonction frais: int list -> int renvoie un entier n'appartenant pas à la liste donnée en argument.
- La fonction prochain: foret -> int list -> int option prend en arguments une forêt
f et une liste de sommets lst. Un appel prochainf lst renvoie Somed où d est un entier apparaissant dansf à profondeur paire et n'appartenant pas à lst si un tel entier d existe. Sinon, prochain f lst revoie None. - La fonction apparie: couplage -> int -> int prend en arguments un couplage cpl et un sommet u couvert par le couplage, et renvoie le sommet apparié, c'est-à-dire l'unique v tel que (min
uv, maxuv ) appartient à cpl . - La fonction gonfle: graphe -> int list -> int list -> int -> int list prend en arguments un graphe grph, un bourgeon brg, un chemin d'augmentation dans le graphe contracté grph/brg, et le nom du nouveau sommet nv, et renvoie le chemin d'augmentation correspondant dans grph.
recherche : graphe -> couplage -> (int list) option
Les questions ci-dessous demandent de donner le code de la ligne 15, des lignes 17-18 et des lignes 24-26, ainsi que d'implémenter la fonction extrait (ligne 20).
Question 19. Écrire une fonction extrait: int list
Question 21. Écrire une fonction edmonds: graphe -> couplage qui prend en entrée un graphe et qui renvoie un couplage maximal.
let rec recherche graphe couplage =
let nc = del (keys graphe) (couverts couplage) in
let foret = ref (List.map (fun x -> N(x,[])) nc) in
let rec aux_voisins u liste_voisins =
let cu = Option.get (find !foret u) in
match liste_voisins with
| [] -> None
| v :: tl ->
match find !foret v with
| None -> let z = apparie couplage v in
foret := extend (extend !foret u v) v z;
aux_voisins u tl
| Some cv when (List.length cv) mod 2 = 0
(* nombre de sommets pair = profondeur impaire *)
-> ...
| Some cv when (List.hd cu) <> (List.hd cv)
-> ...
...
| Some cv
-> let bourgeon = extrait cu cv in
let nv = frais (keys graphe) in
let ng = contracteG graphe bourgeon nv in
let nc = contracteC couplage bourgeon nv in
...
...
...
in
let rec aux_sommets traites =
match prochain !foret traites with
| None -> None
| Some u -> let liste_voisins = del (List.assoc u graphe) traites in
match aux_voisins u liste_voisins with
| None -> aux_sommets (u::traites)
| Some ch -> Some ch
in
aux_sommets
Partie II : Calculs de déterminants
Préliminaires : matrices linéarisées
Question 22. Écrire des fonctions
int read_sqmatrix (int n, int *A, int i, int j)
void write_sqmatrix (int n, int *A, int i, int j, int val)
- read_sqmatrix (
n, A, i, j ) renvoie la valeur du coefficientA_(i, j) oùA est la matricen × n représentée par A ; - write_sqmatrix(
n, A, i, j, val) modifie la valeur du coefficientA_(i, j) pour qu'il soit égal à val.
On supposera que A est un tableau avecn^2 éléments et quei, j ∈ {0, …, n − 1} .
On attend une complexité enO(1) pour ces fonctions, sans la justifier.
Algorithme de Mahajan-Vinay
- les sommets de
G(A) sont les entiers de 0 àn − 1 , - les arcs sont les paires ordonnées
e = (i, j) telles queA_(i, j) ≠ 0 , - le poids
ω(e) d'un arce = (i, j) est égal àA_(i, j) .
- pour tout
i = 0, …, m − 1 , il existe un arc (v_i, v_(i + 1) ) dansG(A) ; - le sommet
v_0 = v_m est le plus petit entier de la suite, appelé la tête deC , notéet(C) ; - le sommet
v_0 n'apparaît qu'au début et à la fin de la suite : pour touti = 1, …, m − 1 , on av_i ≠ v_0 .
La longueur|C| de la marche ferméeC est le nombre d'arcs qui la composent. La longueur deC = v_0…v_m est donc égale àm . Le poidsω(C) d'une marche ferméeC est le produit des poids des arcs (avec multiplicités) qui la composent :
.jpg)
- les têtes sont croissantes :
t(C_1) < t(C_2) < ⋯ < t(C_k) ; - le nombre total d'arcs (en comptant les multiplicités) est égal au nombre de sommets
n deG(A) : ∑_(i = 1)^k|C_i| = n .
Le poids d'une suite de marches fermées est le produit des poids des marches fermées qui la composent :ω(S) = ∏_(i = 1)^k ω(C_i) . La tête deS , notéet(S) , est la tête de la première marche fermée deS : t(S) = t(C_1) .

(1) Donner les marches fermées de longueur au plus 3 et leurs poids.
(2) Donner les suites de marches fermées et leurs poids.
- le signe de la suite de marches fermées
C_1, …, C_k est(− 1)^p ; -
h est la tête de la marche fermée en cours de construction, c'est-à-direh = v_0 ; -
u est le sommet courant, c'est-à-direu = v_m ; -
i est le nombre d'arcs parcourus jusque là, c'est-à-direi = m + ∑_(j = 1)^k|C_j| .
(1) Un arc de poids 1 de
(2) Pour chaque coefficient
(3) Pour chaque coefficient
(4) Pour chaque coefficient

En déduire qu'à chaque suite de marches fermées positive dans
Implémentation
- Un type Fourtable.
- Une fonction Fourtable *create (int n). Un appel create(n) alloue dans le tas une valeur de type Fourtable et renvoie un pointeur t vers celle-ci. La valeur
∗t ainsi créée a la taille nécessaire pour enregistrer les valeurs deF_A lorsqueA est une matricen × n . Les valeurs contenues dans *t sont toutes initialisées à 0 . On supposera que free( t ) a une complexité enO(1) . - Une fonction void write(Fourtable *t, int val, int
p , inth , intv , int i) qui enregistre dans *t l'entier val comme valeur correspondant au sommet⟨p, h, v, i⟩ . - Une fonction int read (Fourtable *t, int
p , inth , intv , int i) qui renvoie la valeur correspondant au sommet⟨p, h, v, i⟩ dans∗t .
On suppose de plus que les fonctions read et write ont une complexité enO(1) , et que la fonction create a une complexité enO(n^3) , où n est la valeur donnée en argument.
Question 29. Écrire une fonction Fourtable *initialise (int n) telle que initialise(n) renvoie un pointeur vers un Fourtable contenant les valeurs de
On attend une complexité en
Question 30. Écrire une fonction Fourtable *remplit (int n , int
On attend une complexité
Question 31. Écrire une fonction int determinant (int n , int
On attend une complexité en
Partie III : Méthode algébrique et probabiliste pour les couplages parfaits
int t [m];
déterminant d'une instantiation de la matrice de Tutte du graphe représenté par
bool parfait (int n , int
qui prend en entrée la matrice d'adjacence linéarisée A d'un graphe
On attend une complexité en
Fin du sujet.
Questions fréquentes
4 questionsSur quels chapitres porte l'épreuve d'informatique C X-ENS MPI 2025 ?Afficher ou masquer la section
Questions fréquentes
4 questionsSur quels chapitres porte l'épreuve d'informatique C X-ENS MPI 2025 ?
Elle porte sur les graphes et les couplages (algorithme d'Edmonds en OCaml), le calcul de déterminants (algorithme de Mahajan-Vinay en C), et un test probabiliste de couplage parfait fondé sur la matrice de Tutte et le lemme de Schwartz-Zippel.
Quelles erreurs le jury a-t-il le plus relevées sur cette épreuve d'informatique X-ENS MPI ?
L'utilisation de fonctions OCaml non autorisées, des copies peu lisibles ou mal rédigées, des raisonnements par l'absurde inutilement compliqués, et du code insuffisamment commenté.
Cette épreuve d'informatique C X-ENS MPI 2025 est-elle difficile ?
Oui, la moyenne des 396 copies est de 9,38/20 avec un écart type de 3,86, et le taux de traitement des dernières questions du sujet chute fortement, jusqu'à 12 % pour la toute dernière.
Faut-il maîtriser à la fois OCaml et le langage C pour cette épreuve X-ENS ?
Oui, la partie I se compose en OCaml et les parties II et III se composent en langage C.
Pas de description pour le moment
