Centrale Informatique MPI 2024Sujet, corrigé et rapport du jury
- Structures de données et allocation dynamique en C
- Complexité algorithmique
- Algorithme des k plus proches voisins
- Tables de hachage
- Programmation concurrente et threads
- Programmation fonctionnelle en OCaml et gestion des exceptions
- Réduction polynomiale et NP-complétude
Téléchargements
Présentation du sujet
DifficileLe jeu de go : implémentation en C, stratégies par k plus proches voisins, hachage de Zobrist, Monte-Carlo en OCaml et NP-difficultéAfficher ou masquer la section
Présentation du sujet
DifficileLe sujet s'intéresse au jeu de go et à la construction d'un programme capable d'y jouer. Il demande d'abord une implémentation en langage C des règles du jeu, puis explore différentes stratégies : la méthode des k plus proches voisins à partir d'une base de données de parties, une recherche de coups locaux optimisée par hachage de Zobrist, une exploration Monte-Carlo parallélisée en OCaml, et enfin une preuve théorique de NP-difficulté d'une version généralisée du jeu par réduction de 3-SAT.
- 1Partie I : introduction au jeu de goPrésentation des règles simplifiées du jeu de go (libertés, groupes, capture, score).
- 2Partie II : implémentation du jeu de goÉcriture en langage C de fonctions manipulant la structure de données représentant le goban.
- 3Partie III : stratégies à partir d'une base de donnéesUtilisation de l'algorithme des k plus proches voisins puis d'une table de hachage de Zobrist pour identifier des coups pertinents, en langage C.
- 4Partie IV : recherche arborescente de Monte-CarloExploration en parallèle de possibilités de parties à l'aide de fils d'exécution concurrents, en langage OCaml.
- 5Partie V : NP-difficultéDémonstration par réduction de 3-SAT qu'une version généralisée de la stratégie gagnante au jeu de go est NP-dure.
Difficile. Le sujet comportait 46 questions et les candidats en ont traité entre 5 et 46, pour une moyenne de 22 questions par candidat, la dernière partie, plus abstraite, ayant permis aux meilleurs candidats de se distinguer.
L'épreuve en chiffres
Moyenne 9,37 / 20 · écart-type 4,02 · 708 présents · où vous situez-vous ?Afficher ou masquer la section
L'épreuve en chiffres
- Moyenne
- 9,37/ 20
- Écart-type
- 4,02
- Présents
- 708
- Coefficient
- 16
- Durée
- 4 h
- 1er quartile
- 6,3
- Médiane
- 9,3
- 3e quartile
- 12,2
Votre note sur 20 à ce sujet, en conditions de concours.
Source : document officiel du concours, épreuve du 3 mai 2024. 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
5 erreurs relevéesNon-respect de la spécification et de la complexité demandée · Question de cours sur les structures de données peu traitée · Application directe du cours peu traitéeAfficher ou masquer la section
Ce qu'a observé le jury
5 erreurs relevéesLe sujet, long et couvrant une partie assez large du programme, demandait de programmer en C et en OCaml et de maîtriser des aspects techniques comme l'allocation dynamique ou les threads. Les candidats ont bien réussi les questions de compétences de base et ont été départagés par les questions plus complexes. Le jury se félicite des bonnes compétences des candidats mais relève que les questions mobilisant directement les connaissances du cours, sans réflexion approfondie, sont peu traitées.
Les erreurs les plus sanctionnées
- 1Non-respect de la spécification et de la complexité demandéeQ8, Q14
Sur certaines copies, seule une partie de la spécification d'une fonction était respectée, et les réponses aux questions 8 et 14 ne respectant pas la contrainte de complexité imposée passent à côté de l'enjeu de la question.
« les réponses aux questions 8 et 14 ne respectant pas cette contrainte passent à côté de l’enjeu de la question »
- 2Question de cours sur les structures de données peu traitéeQ12
Seuls 67% des candidats ont abordé la question demandant de citer une structure de données adaptée vue en cours, et seule une minorité a obtenu tous les points.
« seuls 67% des candidats ont abordé cette question, et seule une minorité a obtenu tous les points »
- 3Application directe du cours peu traitéeQ14
Seuls 35% des candidats ont traité la question demandant de dérouler la méthode diviser pour régner, alors qu'il s'agissait d'une application directe du cours.
« Seuls 35% des candidats ont traité cette question d’application directe du cours, c’est assez peu »
- 4Gestion des exceptions en OCamlQ25
La gestion des exceptions a été la principale difficulté rencontrée par les candidats sur les questions de programmation OCaml.
« La gestion des exceptions a été la principale difficulté pour les candidats »
- 5Gestion du tas en CQ3
Il fallait bien gérer le tas : seule la grille du goban est allouée dynamiquement, la structure représentant le goban elle-même ne l'est pas, et il fallait renvoyer une structure et non un pointeur.
« seule la grille du goban (le champ m) est allouée sur le tas, la structure représentant le goban n’est pas allouée sur le tas »
Ce qui a été bien réussi
- Les candidats maîtrisent globalement bien les langages C et OCaml, la syntaxe n'étant pas un problème pour une large majorité d'entre eux.
- La plupart des candidats savent écrire des preuves, et certaines erreurs mentionnées l'année précédente sont absentes des copies cette année.
- La question 5 sur l'identification des pions d'un groupe a été plutôt bien réussie malgré de petites erreurs.
- Les définitions de cours demandées à la question 37 sont assez bien connues des candidats qui ont traité la question.
Conseils du jury
- Vérifier que la fonction programmée satisfait entièrement la spécification demandée, y compris les contraintes de complexité.
- Ne pas négliger les questions de cours qui demandent de mobiliser directement les connaissances, comme le choix d'une structure de données adaptée.
- Pour une question demandant de proposer un algorithme, une description précise en langage naturel est acceptée sans implémentation en C.
- Soigner la gestion de la mémoire et bien distinguer ce qui doit être alloué dynamiquement de ce qui ne doit pas l'être.
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
Le jeu de go
I Introduction au jeu de go
I.A - Présentation du sujet
I.B - Présentation et règles du jeu de go
Les deux joueurs sont nommés d'après la couleur des pierres qu'ils placent sur le goban : Noir, qui joue en premier et place des pierres noires, et Blanc qui place des pierres blanches. À chaque tour, un joueur place une pierre de sa couleur sur une intersection libre du goban, c'est-à-dire sans pierre. Un coup correspond donc simplement à l'ajout d'une pierre sur une intersection libre, les joueurs ne peuvent pas déplacer une pierre déjà posée.
Nous appellerons configuration du goban, ou simplement goban, l'état du plateau à un moment de la partie, c'est-à-dire la donnée des positions et couleurs de toutes les pierres placées sur le goban. La figure 1 illustre différentes configurations progressives au cours d'une partie possible de go.

Deux pierres d'une même couleur sont dites connectées si elles se trouvent sur des intersections voisines. Nous appellerons groupe de pierres une composante connexe pour cette relation de connexion. Chaque groupe possède un nombre de libertés qui est défini comme le nombre d'intersections libres qui sont voisines d'une pierre du groupe. Une intersection voisine de plusieurs pierres du groupe n'est comptée qu'une seule fois.
Un groupe de pierres auquel on vient de supprimer la dernière liberté est capturé, et les pierres de ce groupe sont alors immédiatement retirées du plateau. Ces notions de libertés et de capture sont illustrées dans la figure 2.

Lorsqu'une pierre est placée, on commence par regarder si elle permet de capturer des pierres adverses, et dans ce cas on retire immédiatement ces pierres capturées du goban. On regarde ensuite si le groupe de la pierre placée possède des libertés. Si elle ne possède aucune liberté, le coup est interdit. Cependant, si elle a permis de capturer des pierres, son groupe a nécessairement au moins une liberté. Il est donc possible de jouer sur une position sans libertés, mais uniquement dans le cas où cela permet de capturer des pierres à l'adversaire.
Une intersection est dite contrôlée par un joueur si une de ses pierres est placée dessus, ou si l'intersection est vide mais que toutes les intersections voisines contiennent des pierres de sa couleur.
La partie s'arrête lorsque toutes les intersections sont contrôlées par un des deux joueurs. Une telle configuration du goban est alors dite finale. Le score de chaque joueur est défini comme le nombre d'intersections contrôlées par ce dernier. Le joueur avec le score le plus élevé remporte la partie.
I.C - Généralités sur le jeu de go
Q 2. Montrer qu'il y a toujours un gagnant dans une configuration finale sur un goban standard de dimension 19.
II Implémentation du jeu de go
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
struct goban_s {
int d;
int* m;
};
typedef struct goban_s goban;
Q 3. Écrire une fonction goban initialisation(int d) prenant en argument un entier d et qui renvoie un goban de dimension
La position d'une intersection du goban sera représentée par une structure de couple d'entiers, contenant la ligne
struct couple_int {
int i;
int j;
};
typedef struct couple_int position;
position pos(int i, int j) {
position p = {.i = i, .j = j};
return p;
}
int lire(goban g, position p) {
return g.m[p.i * g.d + p.j];
}
void ecrire(goban g, position p, int couleur) {
g.m[p.i * g.d + p.j] = couleur;
}
struct voisins_s {
int nb;
position t[4];
};
typedef struct voisins_s voisins;
voisins pos_voisins(goban g, position p) {
int i = p.i;
int j = p.j;
voisins v;
if (...) {v.t[k] = pos(i - 1, j); k = k + 1;}
if (...) {v.t[k] = pos(i + 1, j); k = k + 1;}
if (...) {v.t[k] = pos(i, j - 1); k = k + 1;}
if (...) {v.t[k] = pos(i, j + 1); k = k + 1;}
v.nb = k;
return v;
}
Q 5. Écrire une fonction goban groupe (goban g, position p) qui renvoie un nouveau goban dans lequel seules les pierres du groupe de la pierre en position p sont présentes. Expliquer par une phrase, avant le code de votre fonction, l'approche choisie. Il pourra être utile de commencer par implémenter une fonction auxiliaire récursive, dont la spécification sera clairement explicitée. Donner la complexité obtenue.
Une première tentative pour calculer le nombre de libertés du groupe de la pierre en position
int liberte(goban g, position p) {
goban g1 = groupe(g, p);
int cpt = 0;
int couleur = lire(g, p);
for (int i = 0; i < g.d; i = i + 1) {
for (int j = 0; j < g.d; j = j + 1) {
position p2 = pos(i, j);
if (lire(g1, p2) == couleur) {
voisins v = pos_voisins(g1, p2);
for (int k = 0; k < v.nb; k = k + 1) {
if (lire(g, v.t[k]) == 0) {
cpt = cpt + 1;
}
}
}
}
}
return cpt;
}
Q 7. Proposer des modifications à apporter au code de la fonction liberte pour qu'elle renvoie bien le nombre de libertés, et qu'elle libère les structures créées pendant son appel.
Q 8. Écrire une fonction void retire_groupe(goban g, position p) prenant en argument un goban, une position et qui retire toutes les pierres du groupe de la pierre à cette position. Une complexité linéaire en la taille du groupe à retirer est attendue.
Q 9. Écrire une fonction void joue (goban g, position p, int couleur) prenant en argument un goban, une position et une couleur et qui modifie le goban en ajoutant une pierre de la couleur donnée à la position donnée. On prendra en compte les possibles pierres à retirer et on utilisera des assertions pour vérifier que le coup est valable. On ne vérifiera cependant pas si la configuration du goban a déjà été rencontrée au cours de la partie. Donner la complexité de cette fonction.
III Prédiction et évaluation d'un prochain coup
Pour cela nous nous baserons sur une base de données de 160000 parties de maîtres, jouées sur des gobans standards de dimension 19. Nous avons commencé par décomposer ces parties en 29.4 millions de triplets (configuration du goban, joueur dont c'est le tour, coup suivant choisi), que nous appellerons observations, et qui peuvent être redondantes.
Q 10. À partir des informations sur la base de données, donner une estimation de la profondeur du jeu de go, c'est-à-dire du nombre de coups moyen d'une partie de go. En utilisant la description du jeu ainsi que la profondeur, donner une estimation de la largeur du jeu de go, c'est-à-dire le nombre moyen de coups valables lors d'une partie.
Est-il envisageable de mettre en place un algorithme min-max pour trouver une stratégie gagnante à ce jeu? Justifier.
III.A - K plus proches voisins
Q 11. Proposer une fonction double* k_plus_petits (double* distances, int n, int k) qui prend en entrée un tableau de
Q 12. Proposer une structure de données abstraite qui permette d'améliorer la complexité asymptotique de la fonction k_plus_petits. Rappeler en une phrase une façon d'implémenter cette structure de données ainsi que la complexité obtenue.
Dans le cadre d'un adversaire jouant en temps réel, il est préférable d'avoir une borne précise sur le nombre de comparaisons qui seront réalisées. Nous supposerons désormais que
Nous appellerons comparatif un algorithme prenant en entrée un tableau de distances pour lequel la seule opération autorisée sur les distances du tableau est la comparaison entre deux distances du tableau.
Q 13. Montrer que tout algorithme comparatif renvoyant l'indice du plus petit élément d'un tableau devra réaliser la comparaison du plus petit élément et du second plus petit élément du tableau.
Q 14. Proposer un algorithme qui renvoie les indices des deux plus petits éléments d'un tableau de
III.B - Hachage de Zobrist
Un état local sera modélisé par différents zooms autour d'une position, qui considèrent la disposition du plateau autour d'une intersection en ignorant le reste du goban, appelée motif. Nous noterons
Le but d'un zoom sur une position est d'évaluer un coup potentiel à cette position. Étant donné un motif centré sur une intersection libre sur laquelle il est possible de jouer, nous allons chercher dans la base de données si des configurations possédant ce même motif ont mené à un coup à cette position. Nous considérerons toujours que le centre du motif est une intersection vide.
Q 15. Donner une borne supérieure sur le nombre de motifs distincts pour un zoom
Les fonctions de cette partie sont à écrire en langage
Pour savoir combien de fois un motif a conduit à un coup en son centre, nous allons construire et remplir une table de hachage, pour chaque niveau de zoom. La fonction de hachage associée assignera à chaque motif un entier non signé de 64 bits, de type uint64_t. Cette fonction de hachage particulière se base sur une table d'entiers uint64_t fixés, ayant autant de colonnes que de positions dans le motif, et quatre lignes pour les quatre

couleurs possibles (libre, noir, blanc et hors du goban). Afin de manipuler à la main un nombre raisonnable de bits, sur la table donnée en exemple en figure 4 , nous utilisons des entiers codés sur 8 bits et non sur 64 bits La valeur de hachage est alors calculée comme le ou exclusif (XOR) bit à bit, noté
Formellement, en notant
| a | b | a XOR b |
| 0 | 0 | 0 |
| 1 | 0 | 1 |
| 0 | 1 | 1 |
| 1 | 1 | 0 |
| Couleur et position |
|
|
(0, -1) | (
|
| 0 (vide) | 00001111 | 11100111 | 00011000 | 01111011 |
| 1 (noir) | 11110110 | 11111110 | 01101001 | 10011111 |
| 2 (blanc) | 10100001 | 10001101 | 00110111 | 00110100 |
| 3 (au delà du bord) | 10100000 | 01101010 | 10111011 | 11000010 |
Nous disposons désormais d'une fonction uint64_t hachage(position p, goban g, int zoom) qui calcule la valeur de hachage
De plus, en connaissant la valeur de hachage
Nous nous intéressons maintenant uniquement aux choix des coups de Noir. Il nous faut dénombrer les motifs observés et les motifs choisis dans la base de données de parties. Pour un niveau de zoom fixé, nous stockerons ces valeurs dans une table de hachage, associant un motif à son nombre d'observations et de choix.
Pour un niveau de zoom fixé, dans une configuration du goban où c'est au tour de Noir de jouer, nous observons un motif par position libre de cette configuration. Noir va alors jouer dans une de ces positions libres, et on dit que le motif associé à la position libre où Noir joue est choisi (voir la figure 3).
struct observation_s {
int n; //nombre d'observations du motif au total
int v; //nombre de fois ou ce motif a ete choisi
};
typedef struct observation_s observation;
Nous définissons alors une structure de table de hachage composée d'un tableau d'observations, et d'un entier nt correspondant à sa taille.
struct table_de_hachage_s {
int nt;
observation* t;
};
typedef struct table_de_hachage_s t_hachage;
Pour remplir la table de hachage, nous allons simuler la partie de maître configuration après configuration, en utilisant la fonction maj_hachage pour mettre à jour les valeurs de hachages. Au départ, nous partons donc de la configuration initiale du goban, et nous allons garder en mémoire les valeurs de hachage pour chaque position du goban, et les faire évoluer au fur et à mesure de la partie.
Q 17. Écrire une fonction uint64_t* init_t_hash (int d, int zoom) qui prend en entrée un entier d correspondant à la dimension d'un goban, et un entier zoom correspondant au niveau de zoom d'intérêt, et qui renvoie un tableau d'entiers non signés sur 64 bits alloué dynamiquement, de taille
On considère un goban
void maj_coup(t_hachage table, uint64_t* tableau_hache, goban gN, goban gB, int zoom) {
for (int i = 0; i < gN.d; i = i + 1) {
for (int j = 0; j < gN.d; j = j + 1) {
position p = pos(i, j);
maj_n(table, tableau_hache, gN, p);
maj_v(table, tableau_hache, gN, gB, p);
}
}
}
void maj_t_hache(uint64_t* tableau_hache, goban gN, goban gNs, int zoom) {
for (int i = 0; i < gN.d; i = i + 1) {
for (int j = 0; j < gN.d; j = j + 1) {
position p = pos(i, j);
maj_h(tableau_hache, gN, gNs, zoom, p);
}
}
}
void maj_n(t_hachage table, uint64_t* tableau_hache, goban gN, position p),
void maj_v(t_hachage table, uint64_t* tableau_hache, goban gN, goban gB, position p),
void maj_h(uint64_t* tableau_hache, goban gN, goban gNs, int zoom, position p),
pour que les fonctions maj_coup et maj_t_hache mettent correctement à jour la table de hachage et le tableau
des valeurs de hachage.
Une partie de go sera représentée par une structure de liste simplement chaînée, chaque coup étant représenté par le goban actuel, le joueur qui doit jouer, et un pointeur vers le reste de la partie. Nous supposerons que les parties commencent par le goban vide associé au joueur d'indice 1 (Noir).
struct partie_s {
goban g;
int joueur;
struct partie_s* suivant;
};
typedef struct partie_s partie;
Q 20. Étant donné un tableau des valeurs de hachage initialisé avec le goban initial, montrer qu'au plus
Nous allons représenter toutes les tables de hachages pour les différents niveaux de zoom allant de 1 à
struct table_table_de_hachage {
int nz;
t_hachage* t;
};
typedef struct table_table_de_hachage tt_hachage;
Pour évaluer la pertinence d'un coup dans une configuration, nous allons chercher le plus grand zoom pour lequel le motif a été observé au moins une fois dans la base de données. La qualité estimée de ce coup est alors le nombre de fois où ce motif a été choisi sur le nombre de fois où il a été vu.
Q 21. Écrire une fonction booléenne bool appartient(position p, goban g, t_hachage table, int zoom) qui renvoie vrai si le motif en position
Q 22. Écrire une fonction float evalue_zoom (position p, goban g, t_hachage table, int zoom) qui renvoie la valeur associée au motif, c'est-à-dire le rapport entre le nombre d'observations et le nombre de choix.
Q 23. Écrire une fonction float evaluation(position p, goban g, tt_hachage tt ) qui renvoie l'évaluation du coup en position p sur le goban g. Une fonction de complexité logarithmique en le nombre de zooms
IV Recherche arborescente de Monte-Carlo
Les fonctions de cette partie sont à écrire en langage OCaml. Une annexe rappelant des éléments de langage OCaml, modules Thread et Mutex, module List, type enregistrement et fonction de conversion de type, est proposée en fin de partie.
Commençons par décrire quatre types pour représenter les joueurs, les gobans et les positions:
type joueur = Noir | Blanc
type intersection = Libre | Pierre_noire | Pierre_blanche
type goban = intersection array array
type position = int * int
- liste_coup_prior : goban -> joueur -> (position * float) list: prend un goban et un joueur et renvoie une liste, pour toutes les positions jouables dans le goban pour le joueur, de couples (position, prior) où prior est un flottant entre 0 et 1 correspondant à une estimation de la probabilité de victoire de Noir lorsque le joueur joue en position position.
- strategie_defaut : goban -> joueur -> position: prend un goban et un joueur et renvoie une position où jouer, en suivant une stratégie par défaut définie a priori, et qui peut contenir une part d'aléatoire. Cette fonction renvoie l'exception Pas_de_coup_valide lorsque le plateau est complètement contrôlé et qu'il n'y a plus de coup valide.
- gagnant : goban -> joueur : prend un goban complètement contrôlé et renvoie le joueur gagnant.
- joue : goban -> position -> joueur -> goban: prend un goban, une position et un joueur et renvoie le goban obtenu après que le joueur a joué en cette position. Le goban en entrée n'est pas modifié.
Q 24. Écrire une fonction de signature js : joueur -> joueur qui prend un joueur et renvoie le joueur suivant.
IV.A - Simulation de partie pour évaluer une configuration
Q 25. Écrire une fonction de signature sim_defaut : goban
IV.B - Valeur d'action
- n : le nombre de visites du nœud, entier mutable
- v: le nombre de visites du nœud ayant mené à une victoire de Noir, entier mutable
- prior : la probabilité a priori de victoire de Noir donnée par la fonction liste_coups_prior, flottant
- pos: la position du coup associé au nœud, position
- t : un verrou, de type t . Mutex, qui sera utilisé pour mettre en place des parcours et mises à jour concurrentes de l'arbre.
Afin de stocker ces données, les nœuds de l'arbre seront étiquetés par le type enregistrement etiquette.
type etiquette =
{mutable n:int; mutable v:int; prior:float; pos:position; t:Mutex.t}
1 type arbre = N of etiquette * arbre list
Q 26. Écrire une fonction de signature etiq : arbre -> etiquette qui prend un arbre et renvoie l'étiquette de cette arbre.
Q 27. Écrire une fonction de signature valeurActionNoir : arbre
Q 28. Donner la valeur à maximiser au tour de Blanc en suivant le même principe que pour la valeur d'action de Noir. Nous supposerons par la suite que la fonction valeurActionBlanc : arbre -> float a été également implémentée.
Q 29. Écrire une fonction de signature popargmax : 'a list
IV.C - Descente et remontée dans l'arbre
- une descente dans l'arbre partiel en choisissant à chaque nœud le fils de valeur d'action maximale pour le joueur associé, afin de sélectionner une feuille qui semble prometteuse;
- une simulation de partie depuis cette feuille comme implémentée en partie IV.A, donnant un joueur vainqueur ;
- une remontée qui met à jour le nombre d'observations et de victoires pour Noir dans chaque nœud rencontré, le long de la branche reliant la feuille explorée à la racine de l'arbre.
Ces étapes pourront être exécutées en parallèles par plusieurs fils d'exécution. Il est donc nécessaire de faire attention à ce que deux fils ne rentrent pas en conflit d'écriture et de lecture pour un champ mutable d'une étiquette de l'arbre, ce qui justifie l'intérêt des verrous associés à chaque nœud.
De plus, pour décourager deux fils d'exécution de suivre la même branche lors de la descente, lorsque l'enfant d'un nœud est sélectionné par un fil d'exécution, ce fil va modifier l'étiquette de cet enfant pour lui ajouter des défaites virtuelles, ce qui baissera sa valeur d'action et donc son attractivité du point de vue des autres fils. Nous ajouterons 5 défaites virtuelles à un nœud s'il est sélectionné, en prenant soin d'enlever ces défaites virtuelles lors de la remontée.
Lors de la descente, nous empêcherons ainsi un fil d'exécution de commencer à traiter un nœud, (c'est-à-dire de chercher l'enfant de ce nœud de valeur d'action maximale) si un autre fil d'exécution est déjà en train de traiter ce nœud. Nous attendrons que le fil d'exécution en cours ait ajouté des défaites virtuelles à la branche qu'il a choisi de suivre avant de commencer le traitement.
Q 30. Écrire la fonction descente_remontee_arbre : goban→ joueur→ arbre→ joueur qui prend un goban, le joueur dont c'est le tour et un arbre de jeu, et qui réalise une descente, une simulation et une remontée de l'arbre. Cette fonction renverra le joueur gagnant, et lors de la remontée mettra à jour les champs mutables des nœuds rencontrés. Une attention particulière sera portée sur le fait que cette fonction puisse être exécutée par plusieurs fils d'exécution concurrents.
IV.D - Expansion de l'arbre
Q 31. Écrire une fonction de signature expansion_feuille : goban
Une étape d'expansion consiste à étendre une feuille, qui sera sélectionnée en partant de la racine et en choisissant à chaque nœud le fils ayant été le plus visité.
IV.E - Algorithme complet
Q 33. Écrire une fonction de signature attendre : Thread.t list
Q 34. Écrire une fonction de signature etape_complete : int
Q 35. Montrer que cette fonction termine. Il est notamment attendu de montrer qu'il n'y a pas d'interblocages dans les exécutions de la fonction descente_remontee_arbre en se référant au code proposé en question 30.
Pour estimer le coup à jouer, il reste alors à réaliser plusieurs étapes complètes, par exemple un nombre ns fixé à l'avance. Puis, à la fin de ces étapes, l'algorithme renvoie la position du coup correspondant au nœud le plus visité parmi les fils de la racine.
Le sous-arbre associé à ce coup sera ensuite utilisé comme base pour la suite de la partie, ce qui permet de ne pas repartir de zéro à chaque coup.
Q 36. Écrire une fonction de signature recherche_arborescente : int -> int -> goban -> joueur -> arbre -> arbre * position telle que recherche_arborescente ns nt g j a effectue ns étapes de simulations sur l'arbre a en partant du goban
Éléments du langage OCaml
Thread.t : Le type des fils d'exécution.
Thread.create : ('a -> 'b) -> 'a -> Thread.t: Thread.create funct arg crée un nouveau fil d'exécution dans lequel l'application de funct arg est exécuté en même temps que les autres fils d'exécutions. L'application de Thread.create renvoie le fil d'exécution crée. Le nouveau fil termine quand l'application funct arg termine, soit normalement soit en remontant une exception Thread.Exit ou en remontant d'autres exceptions non rattrapées. Dans ce dernier cas, l'exception non rattrapée est affichée sur la sortie d'erreur standard, mais pas propagée au fil d'exécution parent. De la même manière le résultat de l'application funct arg est perdu et n'est pas directement accessible au fil d'exécution parent.
Thread.join :
Module Mutex
Mutex.t: Le type des mutex.
Mutex.create : unit $\rightarrow$ Mutex.t: Renvoie un nouveau mutex.
Mutex.lock : Mutex.t -> unit: Verrouille le mutex donné. Un seul fil d'exécution peut verrouiller le
mutex à un moment donné. Un fil d'exécution tentant de verrouiller un mutex déjà verrouillé attendra
jusqu'à ce que l'autre fil d'exécution déverouille le mutex.
Mutex.unlock : Mutex.t $\rightarrow$ unit : Déverouille le mutex donné. Les autres fils d'exécution en pause ten-
tant de verouiller le mutex reprennent. Le mutex doit avoir été verrouillé par le fil d'exécution qui appelle
Mutex.unlock.
Module List
List.iter : ('a -> unit) -> 'a list -> unit:iter f [a1; ...; an] applique la fonction f tour à
tour à [a1; ...; an]. Cet application est équivalente à $f a 1 ; f a 2 ; \ldots ; f$ an.
List.map : ('a -> 'b) -> 'a list -> 'b list: map f [a1; ...; an] applique la fonction f à a1,
$\ldots$, an, et construit la liste $[\mathrm{f} \mathrm{a1;} \mathrm{...;} \mathrm{f} \mathrm{an]} \mathrm{avec} \mathrm{les} \mathrm{résultats} \mathrm{renvoyés} \mathrm{par} \mathrm{f}$.
Type enregistrement
type exemple = {mutable a : int; b int}: permet de définir un type enregistrement.
let c = {a = 1; b = 3} permet de définir une variable du type exemple.
c.a permet d'accéder au champ a de c.
c.a<-2 permet de modifier le champ mutable a du type enregistrement c.
Fonction de conversion de type
float_of_int : int -> float: convertit un int en float.
int_of_float : float -> int: convertit un float en int.
V Complexité du problème de la stratégie gagnante au go
Go généralisé
Sortie : Oui s'il existe une stratégie gagnante pour Blanc à partir de cette configuration, non sinon.
Il est possible de montrer que ce problème est NP-dur à partir d'une chaîne de réductions depuis le problème SAT, dont la dernière étape sera montrée dans cette partie.
Q 37. Rappeler la définition de la classe NP, et expliquer ce que signifie être NP-dur et NP-complet. Donner un exemple de problème NP-complet classique autre que SAT et 3SAT, sans prouver son appartenance à cette classe de complexité.
Dans cette partie nous considérons une variante du problème 3SAT, nommée Rectilinear Planar 3SAT, qui est également NP-dur. Tout comme pour 3SAT, les instances de ce problème de décision sont des formules sous forme normale conjonctive avec exactement trois littéraux par clauses, mais désormais chaque clause ne possède que des littéraux positifs, ou que des littéraux négatifs. De plus, la formule en question doit pouvoir se représenter graphiquement de manière planaire rectiligne comme illustré en figure 5, avec toutes les variables alignées horizontalement et les clauses reliées verticalement à leurs variables associées sans aucune intersection. Une telle représentation graphique est également donnée en entrée du problème avec la formule, et n'a donc pas besoin d'être trouvée.

Q 38. Expliquer pourquoi si Blanc parvient à relier sa réserve à un gadget (d) de la figure 6, alors Noir ne peut plus capturer les pierres blanches de la réserve.
Ce gadget à atteindre pour sécuriser le groupe de pierres sera nommé des yeux. En tentant à tout prix de relier sa réserve à des yeux, Blanc va progressivement relier sa réserve à des gadgets, qui sont des dispositions
.jpg)

Q 40. Décrire des modifications à apporter au gadget (a) pour obtenir un gadget représentant un branchement mais dans lequel c'est Noir qui décide si le tuyau de gauche ou de droite sera relié au tuyau du haut. On rappelle qu'on se place toujours dans le cas où Blanc joue en premier dans les gadgets.
Nous utiliserons, pour ce nouveau gadget, la même représentation simplifiée que le gadget (a) en remplaçant le
Q 41. Que permet de réaliser le gadget (b) ? Justifier.
Q 42. Justifier que le gadget (c) permet, en arrivant par le bas, de tester si le tuyau transversal du haut a déjà été relié à la réserve de pierres blanches par le passé ou pas. On pourra notamment justifier que la partie se décidera sur ce gadget précis, et l'issue en sera uniquement déterminée par le fait que le tuyau transversal du haut ait été préalablement relié à la réserve ou non.
Nous nous intéressons désormais à l'assemblage de ces gadgets pour former un circuit, qui pourra être représenté en utilisant les notations simplifiées. Dans un premier temps, nous allons simuler la valuation de chaque variable par le fait d'avoir relié une section à la réserve ou non.
Q 43. Vérifier que pour chaque variable
Q 44. Vérifier qu'il existe, pour toute valuation des variables, une stratégie permettant à Blanc de relier à la réserve de pierres blanches les sections associées à cette valuation.

.jpg)
Q 46. En utilisant les questions précédentes, conclure en proposant une réduction complète de Rectilinear Planar 3SAT au jeu de go généralisé.
Questions fréquentes
3 questionsSur quels chapitres porte le sujet d'informatique MPI 2024 de Centrale-Supélec ?Afficher ou masquer la section
Questions fréquentes
3 questionsSur quels chapitres porte le sujet d'informatique MPI 2024 de Centrale-Supélec ?
Le sujet porte sur les structures de données et l'allocation dynamique en C, l'algorithme des k plus proches voisins, les tables de hachage, la programmation concurrente avec des threads, la programmation fonctionnelle en OCaml, et la NP-complétude par réduction de 3-SAT.
Le sujet d'informatique MPI 2024 sur le jeu de go est-il difficile ?
Le rapport indique que le sujet, long avec 46 questions, a été traité en moyenne à hauteur de 22 questions par candidat, la dernière partie plus abstraite sur la NP-difficulté ayant permis aux meilleurs candidats de se distinguer.
Quelles erreurs le jury a-t-il le plus relevées sur ce sujet d'informatique MPI 2024 Centrale ?
Le jury relève un non-respect fréquent des contraintes de complexité demandées dans les spécifications, des questions de cours comme le choix d'une structure de données peu traitées, et des difficultés dans la gestion des exceptions en OCaml.
Pas de description pour le moment
