Rapport sur Lumen
Table of Contents
Présentation du problème
Lumen est un problème du site Codingame de gestion de tableaux à deux dimensions. L’énoncer du problème est assez simple. Nous nous sommes fait enfermés dans une pièce éclairée par des bougies, et le but est de se cacher. Il faut donc trouver toutes les zones de la pièce qui ne sont pas éclairées par ces bougies. La pièce dans laquelle nous nous trouvons est un carré de coté N, et est éclairé par un nombre aléatoire de bougies (il peut ne pas y en avoir du tout). Ces bougies éclairent tout autour d’elles une certaine zone la pièce, et peuvent être plus ou moins puissante. Elles ont une puissance maximale L allant de 3 à 10 selon les tests (dans une même pièce toutes les bougies ont la même puissance). Au niveau de l’emplacement de la bougie, la quantité de lumière est maximale, elle vaut L, et à chaque fois que l’on s’éloigne de la bougie, l’éclairement de la case diminue de 1. Si une case est éclairée par deux bougies, alors l’éclairement est maximale sur cette dernière.
La pièce dans laquelle on se trouve est représentée sous la forme d’un tableau à deux dimensions, où chaque case peut prendre deux valeurs.
- X: C’est une case normale, sans particularitées
- C: C’est une case sur laquelle se situe une bougie
Le but final est de compter le nombre de cases qui ne sont pas du tout éclairées.
Voici ci-dessous un exemple de la façon dont fonctionnent les bougies. Les valeurs représentent le taux d’éclairement de chaque case. Quand une case a pour valeur 0, cela signifit que c’est une zone d’ombre où l’on peut se cacher. Dans l’exemple il y a 9 zones d’ombres.
X X X X X 2 2 2 1 0 X C X X X 2 3 2 1 0 X X X X X -----> 2 2 2 1 0 X X X X X 1 1 1 1 0 X X X X X 0 0 0 0 0
Métode de résolution
Description de la stratégie
Ma stratégie est la suivante, remplir un tableau similaire à celui de l’exemple ci dessus, puis compter le nombre de cases non éclairées et l’afficher. Comme on nous demande uniquement le nombre de cases sombres, il n’est pas nécessaire de s’occuper de la valeur exacte de l’éclairement de chaque case du tableau, il suffit de leur assigner une valeur différente de 0. Ainsi, seules les cases non éclairées auront pour valeur 0. J’ai donc diviser mon programme en plusieurs parties afin de mettre en place ma stratégie de résolution.
Partie 1: Instanciation des variables
int N; scanf("%d", &N); // Dimension du tableau int L; scanf("%d", &L); // Luminosité char tab[255][255]; // Grille int i; int j; int h; int l; int count=0;
Dans cette première partie, on va définir les variables qui nous seront utiles pour coder la suite du programme. On récupère d’abord les informations de l’énoncé qui sont les dimensins du tableau et la puissance des bougies. Puis on définit le tableau dans lequel on va faire le calcul des cases sombres. Enfin, on crée un certain nombre de compteurs qui nous servirons par la suite.
Partie 2: Création du tableau
for (i = 0; i < N; i++) { for (int j = 0; j < N; j++) { char cell[4]; scanf("%s", cell); // Récupération du caractère de chaque case if(cell[0]=='C'){ tab[i][j]='C'; // Place les bougies }else{ tab[i][j]='0'; // Remplace les X par des 0 } } }
Dans cette seconde partie, on crée le tableau avec lequel on va travailler dans la suite du programme. Pour cela, on récupère chaque élement du tableau initial qui nous est fourni par Codingame, et on le modifie avant de l’ ajouter à notre tableau. Si l’élement est une bougie, on l’ajoute telquel à notre tableau, si c’est une autre case, on ajoute un 0 au tableau.
Partie 3: Boucle principale
for (i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if((tab[i][j]>='A')&&(tab[i][j]<='Z')){ // Si on détecte une bougie for(l = 1;l<L; l++){ if((j-l>=0)&&(tab[i][j-l]<L+48)){ // Place la lumière à gauche de la bougie tab[i][j-l]+=1; if(i!=0){ for(h=1;h<L;h++){ tab[i-h][j-l]+=1; } } if(i!=N-1){ for(h=0;h<L;h++){ tab[i+h][j-l]+=1; } } } if((j+l<N)&&(tab[i][j+l]<L+48)){ // Place la lumière à droite de la bougie tab[i][j+l]+=1; if(i!=0){ for(h=1;h<L;h++){ tab[i-h][j+l]+=1; } } if(i!=N-1){ for(h=0;h<L;h++){ tab[i+h][j+l]+=1; } } } if((i-l>=0)&&(tab[i-l][j]<L+48)){ // Place la lumière sur la colonne de la bougie (au dessus) tab[i-l][j]+=1; } if((i+l<N)&&(tab[i+l][j]<L+48)){ // Place la lumière sur la colonne de la bougie (en dessous) tab[i+l][j]+=1; } } } } }
Dans cette troisième partie, on calcul les zones éclairées et les zones sombres du tableau. Si on détecte une bougie, on va incrémenter de 1 les cases autour (en fonction de la puissance de la bougie, on en incrémentera plus ou moins). Puis on va répeter le processus pour chaque case du tableau.
Partie 4: Comptage des 0
for (i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if(tab[i][j]=='0'){ count++; } } }
Dans cette quatrième partie, on va simplement parcourir le tableau à l’aide d’une double boucle, et compter le nombre de 0. Si on détecte un 0, on incrémente de 1 le compteur.
Partie 5: Affichage du résultat
printf("%d\n",count); return 0;
Enfin, dans cette cinquième boucle, on va simplement afficher le résultat du programme, c’est-à-dire le compteur dont on s’est servi précedement.
Détail algorithmique de la méthode
Dans ce programme, on se base essentiellement sur un mécanisme que je vais décrire ci-dessous. Le fait d’incrémenter chaque case éclairé, afin de créer une zone carrée, dont le centre est la bougie.
Pour ce faire, on va d’abord détecter les bougies. Dès qu’une bougie est détectée, on va regarde si les cases que l’on souhaite modifier existent, si elles existent on va alors les incrémenter de 1. Mais rentrons plus en détail sur la façon dont on regarde les cases. On va tout d’abord s’occuper des cases qui sont à gauches de la bougies. Alors on va incrémenter la case qui se situe à gauche et sur la même ligne que la bougie. A partir de cette case, on va incrémenter toutes les cases qui se situent en dessous et au dessus d’elle. Puis on va utiliser le même processus à droite. Ensuite, on va répéter l’opération pour des cases de plus en plus éloignées de la bougie, jusqu’à ce que la bougie ne soit plus assez puissante pour éclairer la case d’après.
Enfin, on va éclairer les cases situées sur la colonne de la bougie, en haut et en bas.
Afin de mieux comprendre ce mécanisme, voici un schéma plus détaillé du fonctionnement de cette boucle. La bougie représentée a une puissance de 3.
Dans ce schéma, les cases rouges représentent les bougies, et les cases bleues sont les cases que l’on cherche à remplir. Les couleurs les plus foncées sont celles que l’on rempli en premier. Lors de la première étape, on rempli le début de la partie gauche, puis on va remplir le début de la partie droite. A l’étape 3, on voit que le processus lors duquel l’on rempli à gauche et à droite est terminé. Enfin, lors de l’étape 4 on a rempli la colonne de la bougie. Le remplissage de la zone est donc terminée, et toutes les cases blanches sont des cases d’ombres, où on peut se cacher.
Code commenté ayant résolu le problème
Voici ci dessous le code complet m’ayant permis de résoudre le problème.
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <stdbool.h> int main(){ // Initialisation des variables int N; scanf("%d", &N); // Dimension du tableau int L; scanf("%d", &L); // Luminosité char tab[255][255]; // Grille int i; int j; int h; int l; int count=0; // Création du tableau for (i = 0; i < N; i++) { for (int j = 0; j < N; j++) { char cell[4]; scanf("%s", cell); // Récupération du caractère de chaque case if(cell[0]=='C'){ tab[i][j]='C'; // Place les bougies }else{ tab[i][j]='0'; // Remplace les X par des 0 } } } // Boucle principale for (i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if((tab[i][j]>='A')&&(tab[i][j]<='Z')){ // Si on détecte une bougie for(l = 1;l<L; l++){ if((j-l>=0)&&(tab[i][j-l]<L+48)){ // Place la lumière à gauche de la bougie tab[i][j-l]+=1; if(i!=0){ for(h=1;h<L;h++){ tab[i-h][j-l]+=1; } } if(i!=N-1){ for(h=0;h<L;h++){ tab[i+h][j-l]+=1; } } } if((j+l<N)&&(tab[i][j+l]<L+48)){ // Place la lumière à droite de la bougie tab[i][j+l]+=1; if(i!=0){ for(h=1;h<L;h++){ tab[i-h][j+l]+=1; } } if(i!=N-1){ for(h=0;h<L;h++){ tab[i+h][j+l]+=1; } } } if((i-l>=0)&&(tab[i-l][j]<L+48)){ // Place la lumière sur la colonne de la bougie (au dessus) tab[i-l][j]+=1; } if((i+l<N)&&(tab[i+l][j]<L+48)){ // Place la lumière sur la colonne de la bougie (en dessous) tab[i+l][j]+=1; } } } } } // Comptage des 0 for (i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if(tab[i][j]=='0'){ count++; } } } // Affichage du résultat printf("%d\n",count); return 0; }
Présentation des tests
Afin de coder ce programme et de le valider, il a fallut passer plusieurs tests. Alors voici ci dessous les tests qui m’ont permi de résoudre ce problème.
Test 1
Test 2
Test 1 à 9
========Test numero 1 passe======== ==============Sortie============== 9 ================================== ========Test numero 2 passe======== ==============Sortie============== 0 ================================== ========Test numero 3 passe======== ==============Sortie============== 2 ================================== ========Test numero 4 passe======== ==============Sortie============== 4 ================================== ========Test numero 5 passe======== ==============Sortie============== 14 ================================== ========Test numero 6 passe======== ==============Sortie============== 34 ================================== ========Test numero 7 passe======== ==============Sortie============== 9 ================================== ========Test numero 8 passe======== ==============Sortie============== 90 ================================== ========Test numero 9 passe======== ==============Sortie============== 9 ==================================
Remarques sur les tests précedents
- Test 2 à 6
Les tests 2 à 6 n’ont rien de particuliers, ils permettent uniquement de tester le programme dans différents cas.
- Test 7
Ce test est intéressant car il permet de voir si le programme fonctionne lprsqu’il n’y a aucune bougie dans la pièce.
- Test 8
Ce test est celui où le tableau est le plus grand de tous. Il permet de voir si le programme fonctionne avec de très grandes valeurs.
Description d’une autre solution au problème
J’ai choisi de présenter la solution de l’utilisateur Alain Delpuch car elle est vraiment intéressante. En effet, elle utilise un mécanisme de récursion, et est composée de très peu de lignes de codes. C’est donc une solution très intelligente, et bien plus optimisée que la mienne. Alors voici ci dessous l’explication de cette solution en plusieurs parties.
Partie 1: Définition de la fonction
F(unsigned int i, unsigned int j, int l){ if ( i >= N || j >= N || l < map[i][j] ) return; map[i][j] = l--; F( i-1 , j-1 , l) ; F( i-1 , j , l) ; F( i-1 , j+1 , l) ; F( i , j-1 , l) ; ; F( i , j+1 , l) ; F( i+1 , j-1 , l) ; F( i+1 , j , l) ; F( i+1 , j+1 , l) ; }
Dans cette première partie, l’utilisateur définit sa fonction récursive qui sera le coeur de son programme. Il va d’abord tester si les valeurs de i et j qui lui sont données (les coordonnées de la case traitée) ne sont pas sur un bord de la pièce, auquel cas, il ne pourra pas executer le programme pour ces valeurs là. Ensuite, il va enlever 1 à la valeur de la lumière, ce qui fait que sa récursion va s’arréter quand la lumière ne sera pas assez puissante pour atteindre la case suivante.
Enfin, il va appeler sa fonction avec les coordonnées de chaque case qui entoure la bougie. Puis, grace au mécanisme de récursion, il va rappeler sa fonction avec la valeur de chaque case qui entoure les cases qui entourent la bougie, et ainsi de suite. De façon plus claire, il va remplir la zone autour de la bougie à l’aide de carré. Afin de mieux comprendre ce mécanisme, voici un schéma explicatif de son fonctionnement.
Dans ce schéma, les cases rouges représentent la bougie, et le cases bleues, les zones éclairées par la bougie. Si une case est en bleue foncé, cela signifit que le programme l’a incrémenté au moins deux fois. Ici, la puissance de la bougie est de 3. Lors de la première étape, on va remplir les cases se situant tout autour de la bougie. Puis dans la seconde étape, on va appliquer la récursion pour la case se trouvant à la position (i-1,j-1), où (i,j) sont les coordonnées de la bougie. Ensuite, dans l’étape 3 on va en plus appliquer la récursion aux cases de coordonnées (i-1,j) et (i-1,j+1). Enfin, dans l’étape 4 on va appliquer la récusrion à toutes les autres cases, ce qui va nous permettre d’avoir la zone complète éclairée par la bougie.
Partie 2: Calcul de la solution
scanf("%d %d\n", &N, &L); for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { char buf[2]; scanf("%s",buf); F( i , j , *buf=='X' ? 0 : L); } }
Dans cette deuxième partie, l’utilisateur va pour chaque caractère du tableau tester si c’est une bougie. Si s’en est une, alors on va appliquer la fonction récursive aux coordonnées de la bougie afin de compléter le tableau en conséquence
Partie 3: Comptage des 0 et affichage de la solution
int result = 0; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { result += map[i][j] == 0; } } printf("%d",result);
Dans cette troisième partie, on va simplement compter le nombre de 0 restant dans le tableau, et on va afficher la valeur du compteur, qui correspond à notre résultat final.
Voici ci dessous le code commenté de l’utilisateur Alain Delpuch en entier.
#include <stdio.h> // Instanciation des variables int map[25][25]; int N,L; void // Definition de la fonction F(unsigned int i, unsigned int j, int l){ if ( i >= N || j >= N || l < map[i][j] ) return; map[i][j] = l--; F( i-1 , j-1 , l) ; F( i-1 , j , l) ; F( i-1 , j+1 , l) ; F( i , j-1 , l) ; ; F( i , j+1 , l) ; F( i+1 , j-1 , l) ; F( i+1 , j , l) ; F( i+1 , j+1 , l) ; } main(){ // Calcul de la solution scanf("%d %d\n", &N, &L); for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { char buf[2]; scanf("%s",buf); F( i , j , *buf=='X' ? 0 : L); } } // Affichage du résultat int result = 0; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { result += map[i][j] == 0; } } printf("%d",result); }
Bilan de ce travail
Ce travail m’a permi de me perfectionner sur les tableaux, et d’apprendre différentes techniques afin de remplir une zone de forme carré en connaissant le centre. En effet, la méthode récursive d’Alain Delpuch est assez intéressante et m’a permi de voir le problème sous un autre angle. Enfin, l’écriture de ce rapport m’a permi d’expérimenter le module babel d’orgmode, permettant de coder directement depuis un document, sans passer par le site Codingame, ou un autre IDE.