web-dev-qa-db-fra.com

Démineur maître de Google Code Jam (2014) Tour de qualification

Ceci est un problème de la phase de qualification de Google Code Jam (qui est terminée maintenant). Comment résoudre ce problème?

Remarque: Si vous utilisez une méthode différente de celle décrite dans les réponses, partagez-la afin que nous puissions approfondir nos connaissances sur les différentes manières de résoudre ce problème.

Déclaration du problème:

Démineur est un jeu d'ordinateur qui est devenu populaire dans les années 1980 et qui est toujours inclus dans certaines versions du système d'exploitation Microsoft Windows. Ce problème a une idée similaire, mais il ne suppose pas que vous avez joué au dragueur de mines.

Dans ce problème, vous jouez à un jeu sur une grille de cellules identiques. Le contenu de chaque cellule est initialement masqué. Il y a M mines cachées dans M différentes cellules de la grille. Aucune autre cellule ne contient de mines. Vous pouvez cliquer sur n'importe quelle cellule pour la révéler. Si la cellule révélée contient une mine, le jeu est terminé et vous perdez. Sinon, la cellule révélée contiendra un chiffre compris entre 0 et 8 inclus, qui correspond au nombre de cellules voisines contenant des mines. Deux cellules sont voisines si elles partagent un coin ou un bord. De plus, si la cellule révélée contient un 0, tous les voisins de la cellule révélée sont également automatiquement révélés, de manière récursive. Lorsque toutes les cellules ne contenant pas de mines ont été révélées, la partie se termine et vous gagnez.

Par exemple, une configuration initiale du tableau peut ressembler à ceci ('*' désigne une mine et 'c' est la première cellule sur laquelle vous avez cliqué):

*..*...**.
....*.....
..c..*....
........*.
..........

Il n'y a pas de mines adjacentes à la cellule sur laquelle vous avez cliqué. Ainsi, lorsqu'elle est révélée, elle devient un 0 et ses 8 cellules adjacentes sont également révélées. Ce processus se poursuit et aboutit au tableau suivant:

*..*...**.
1112*.....
00012*....
00001111*.
00000001..

À ce stade, il y a toujours des cellules non révélées qui ne contiennent pas de mines (indiquées par des caractères '.'), Le joueur doit donc cliquer à nouveau pour continuer la partie.

Vous voulez gagner le jeu le plus rapidement possible. Il n'y a rien de plus rapide que de gagner en un clic. Compte tenu de la taille du plateau (R x C) et du nombre de mines cachées M, est-il possible (bien que peu probable) de gagner en un clic? Vous pouvez choisir où vous cliquez. Si cela est possible, imprimez n'importe quelle configuration de mine valide et les coordonnées de votre clic, en suivant les spécifications de la section Sortie. Sinon, indiquez "Impossible".

Ma solution essayée:

Donc, pour la solution, vous devez vous assurer que chaque nœud non-mien est dans une matrice 3x3 avec d'autres nœuds non-miens, ou une matrice 3x2 ou 2x2 si le nœud est sur un bord de la grille; appelons cela un 0Matrix. Ainsi, tous les nœuds d'un 0Matrix ont tous des voisins autres que les miens.

Tout d'abord, vérifiez si moins de mines sont nécessaires ou moins de nœuds vides

if(# mines required < 1/3 of total grid size)
    // Initialize the grid to all clear nodes and populate the mines
    foreach (Node A : the set of non-mine nodes)
        foreach (Node AN : A.neighbors)
            if AN forms a OMatrix with it's neighbors, continue
            else break;
        // If we got here means we can make A a mine since all of it's neighbors 
        // form 0Matricies with their other neighbors
    // End this loop when we've added the number of mines required

else
    // We initialize the grid to all mines and populate the clear nodes
    // Here I handle grids > 3x3; 
    // For smaller grids, I hard coded the logic, eg: 1xn grids, you just populate in 1 dimension

    // Now we know that the # clear nodes required will be 3n+2 or 3n+4
    // eg: if a 4x4 grid need 8 clear nodes : 3(2) + 2

    For (1 -> num 3's needed)
        Add 3 nodes going horizontally
        When horizontal axis is filled, add 3 nodes going vertically
           When vertical axis is filled, go back to horizontal then vertical and so on.

    for(1 -> num 2's needed)
        Add 2 nodes going horizontally or continuing in the direction from above
        When horizontal axis is filled, add 2 nodes going vertically

Par exemple, disons que nous avons une grille 4x4 nécessitant 8 nœuds propres, voici les étapes:

// Initial grid of all mines
* * * *
* * * *
* * * *
* * * *

// Populating 3's horizontally
. * * *
. * * *
. * * *
* * * *     

. . * *
. . * *
. . * *
* * * *    

// Populating 2's continuing in the same direction as 3's
. . . *
. . . *
. . * *
* * * *        

Autre exemple: grille 4x4 avec 11 nœuds clairs nécessaires; sortie:

. . . .
. . . .
. . . *
* * * * 

Autre exemple: grille 4x4 avec 14 nœuds clairs nécessaires; sortie:

// Insert the 4 3's horizontally, then switch to vertical to insert the 2's
. . . .
. . . .
. . . .
. . * *  

Nous avons maintenant une grille complètement remplie qui peut être résolue en un clic si nous cliquons sur (0, 0).

Ma solution fonctionne dans la plupart des cas, mais la soumission n'a pas été acceptée (j'ai vérifié un fichier de sortie complet de 225 cas), alors je suppose qu'elle pose quelques problèmes et je suis à peu près certaine qu'il existe de meilleures solutions.

30
Joshua Kissoon

Algorithme

Définissons d'abord N, le nombre de cellules non-mines:

N = R * C - M

Une solution simple consiste à remplir une zone de N cellules non minées, ligne par ligne, de haut en bas. Exemple pour R=5, C=5, M=12:

c....
.....
...**
*****
*****

C'est:

  • Commencez toujours dans le coin supérieur gauche.
  • Remplissez N / C lignes avec des mines autres que les mines de haut en bas.
  • Remplissez la ligne suivante avec N % C non-mines de gauche à droite.
  • Remplissez le reste avec des mines.

Vous devez vous préoccuper de quelques cas particuliers.

Single non-mien

Si N=1, toute configuration est une solution correcte.

Une rangée ou une colonne

Si R=1, remplissez simplement la variable N non-mines de gauche à droite. Si C=1, remplissez les lignes N avec un (unique) fichier non mien.

Trop peu de non mines

Si N est pair, il doit être> = 4.

Si N est impair, il doit être> = 9. De même, R et C doivent être> = 3.

Sinon, il n'y a pas de solution.

Impossible de remplir les deux premières lignes

Si N est pair et que vous ne pouvez pas remplir au moins deux lignes avec des mines autres que des mines, remplissez les deux premières lignes avec N / 2 non mines.

Si N est impair et que vous ne pouvez pas remplir au moins deux lignes avec des mines non mines et une troisième ligne avec 3 non mines, remplissez les deux premières lignes avec (N - 3) / 2 non mines et la troisième ligne avec 3 non mines.

Seul non-mien dans la dernière rangée

Si N % C = 1, déplace le dernier non-mien de la dernière ligne complète à la suivante.

Exemple pour R=5, C=5, M=9:

c....
.....
....*
..***
*****

Résumé

Il est possible d'écrire un algorithme qui implémente ces règles et renvoie une description du champ de mine résultant dans O(1). Dessiner la grille prend O(R*C), bien sûr. J'ai également écrit une implémentation en Perl basée sur ces idées, qui a été acceptée par le juge Code Jam.

24
nwellnhof

Il existe une solution plus générale à ce problème qui passe à la fois dans les cas de test petits et grands. Cela évite de penser à tous les cas spéciaux, il ne se soucie pas des dimensions du tableau et ne nécessite aucun suivi.

ALGORITHME

L'idée de base est de commencer avec une grille pleine de mines et de les supprimer en partant de la cellule {0, 0} jusqu'à ce qu'il y ait le nombre correct de mines sur le plateau.

De toute évidence, il est nécessaire de trouver un moyen de déterminer les mines à enlever et à quel moment il est impossible de retirer le nombre correct de mines. Pour ce faire, nous pouvons garder un int[][] qui représente le tableau. Chaque cellule avec une mine contient -1 et celles sans mines contiennent un entier qui correspond au nombre de mines adjacentes à la cellule; les mêmes que dans le jeu réel.

Définissez également le concept de «frontière», qui comprend toutes les cellules non minières qui sont non nulles, c'est-à-dire les cellules dont les mines sont adjacentes.

Par exemple la configuration:

c . *
. . *
. . *
* * *

Serait représenté comme:

 0  2 -1
 0  3 -1
 2  5 -1
-1 -1 -1

Et la frontière contiendrait les cellules avec les valeurs: 2, 3, 5, 2

En retirant les mines, la stratégie est la suivante:

  • Trouvez dans la frontière une cellule ayant la même valeur que le nombre de mines restantes à éliminer. Ainsi, dans l'exemple ci-dessus, si nous avions 5 mines supplémentaires à éliminer, les cellules de valeur 5 situées sur la frontière seraient choisies.
  • A défaut, nous avons choisi la plus petite cellule frontière. Donc, l'un des 2 dans l'exemple ci-dessus.
  • Si la valeur de la cellule choisie est supérieure au nombre de mines qu'il reste à éliminer, il est impossible de construire cette carte, renvoyez donc false.
  • Sinon, supprimez toutes les mines entourant la cellule frontière choisie.
  • Répétez l'opération jusqu'à ce que le nombre correct de mines soit présent sur le tableau - les contraintes du problème ont été satisfaites.

En Java, cela ressemble à:

// Tries to build a board based on the nMines in the test case
private static boolean buildBoard(TestCase t) throws Exception {
    if (t.nMines >= t.Board.rows() * t.Board.cols()) { 
        return false;
    }
    // Have to remove the cell at 0,0 as the click will go there
    t.Board.removeMine(0, 0);
    while (!t.boardComplete()) {
        Cell c = nextCell(t);
        // If the value of this cell is greater than what we need to remove we can't build a board
        if (t.Board.getCell(c.getRow(), c.getCol()) > t.removalsLeft()) {
            return false;
        }
        t.Board.removeNeighbourMines(c.getRow(), c.getCol());
    }

    return true;
}

// Find frontier cell matching removals left, else pick the smallest valued cell to keep things balanced
private static Cell nextCell(TestCase t) {
    Cell minCell = null;
    int minVal = Integer.MAX_VALUE;
    for (Cell c : t.Board.getFrontier()) {
        int cellVal = t.Board.getCell(c.getRow(), c.getCol());
        if (cellVal == t.removalsLeft()) {
            return c;
        }
        if (cellVal < minVal) {
            minVal = cellVal;
            minCell = c;
        }
    }
    if (minCell == null) {
        throw new NullPointerException("The min cell wasn't set");
    }
    return minCell;
}

PREUVE/INTUITION

Tout d'abord, un tableau est défini comme valide s'il peut être résolu en un seul clic, même s'il n'y a qu'une seule cellule sur le tableau où ce clic peut se produire. Par conséquent, pour qu’un tableau soit valide, toutes les cellules autres que les mines doivent être adjacentes à une cellule de valeur 0. Si ce n’est pas le cas, la cellule est définie comme suit: inaccessible. En effet, nous savons avec certitude que toutes les cellules adjacentes à une cellule 0 sont des mines, elles peuvent donc être révélées en toute sécurité et le jeu le fera automatiquement pour le joueur.

Un élément clé à prouver pour cet algorithme est qu’il est toujours nécessaire d’enlever toutes les mines entourant la plus petite cellule frontière afin de maintenir le tableau dans un état {valide}. Ceci est assez facile à prouver en tirant simplement sur un tableau (comme celui ci-dessus) et en choisissant la cellule de valeur la plus basse (dans ce cas, la partie supérieure droite 2). Si une seule mine est retirée, le tableau ne serait pas valide, ce serait dans l'un ou l'autre de ces deux états:

 0  1  1
 0  2 -1
 2  5 -1
-1 -1 -1

ou

 0  1 -1
 0  2  2
 2  5 -1
-1 -1 -1

dont les deux ont inaccessibles cellules.

Il est donc maintenant vrai que le fait de toujours choisir la plus petite cellule frontière gardera le tableau dans un état valide. Mon premier réflexe a été que continuer à choisir ces cellules passerait par tous les états valides, mais ce n’est pas correct. Ceci peut être illustré par un scénario de test tel que 4 4 7 (il y a donc 9 cellules non-mines). Ensuite, considérons le tableau suivant:

 0  2 -1 -1
 2  5 -1 -1
-1 -1 -1 -1
-1 -1 -1 -1

Continuer à choisir la plus petite cellule frontière peut entraîner l’algorithme suivant:

 0  2 -1 -1
 0  3 -1 -1
 0  3 -1 -1
 0  2 -1 -1

Ce qui signifie qu'il est maintenant impossible de retirer une seule mine pour compléter le tableau dans ce cas. Cependant, le choix d’une cellule frontière correspondant au nombre de mines restantes (s’il en existe une) garantit que la cellule 5 aurait été choisie, ce qui donnerait un carré 3x3 de non-mines et offrirait une solution correcte au cas d’essai.

À ce stade, j'ai décidé d'essayer tous les cas de test de l'algorithme dans la plage suivante:

0 < R < 20
0 < C < 20
0 ≤ M < R * C

et a constaté qu’il avait réussi à identifier correctement toutes les configurations impossibles et à construire ce qui ressemblait à des solutions sensibles aux configurations possibles.

L’intuition de choisir la cellule frontière avec la même valeur que le nombre de mines restantes (le cas échéant) est correcte, c’est que cela permet à l’algorithme de trouver des configurations pour les solutions nécessitant un impair nombre de non-mines .

Lors de la mise en œuvre initiale de cet algorithme, j'avais l'intention d'écrire des heuristiques qui construisaient la zone non-mine dans un arrangement carré. En considérant à nouveau le cas de test 4 4 7, ceci se terminerait ainsi:

 0  0  2 -1
 0  1  4 -1
 2  4 -1 -1
-1 -1 -1 -1

remarquez comment nous avons maintenant un 1 sur la frontière qui garantirait que la cellule finale supprimée complète le carré pour donner:

c . . *
. . . *
. . . *
* * * *

Cela signifierait que les heuristiques changeraient légèrement pour:

  • Choisissez la plus petite cellule frontière
  • En cas d'égalité des voix, choisir la première cellule ajoutée à la liste des frontières

Cela pourrait être mis en œuvre en conservant une FIFO _ file de cellules frontières, mais je me suis vite rendu compte que c'était plus compliqué qu'il n'y paraissait. Cela est dû au fait que les valeurs des cellules frontières sont interdépendantes et que vous devez donc veiller à conserver la collection de cellules frontières dans le bon état et à ne pas utiliser la valeur de cellules dans aucun type de valeur de hachage, etc. I Je suis sûr que cela pourrait être fait, mais après avoir réalisé que le simple ajout de l'heuristique supplémentaire pour sélectionner toutes les cellules dont les valeurs étaient égales aux suppressions restantes fonctionnait, cela semblait être l'approche la plus simple.

15
Choc13

Ceci est mon code . J'ai résolu en prenant différents cas comme si number of rows=1 ou number of columns=1 ou si number of mines=(r*c)-1 et quelques autres cas.

La position sur laquelle vous souhaitez cliquer est placée à a[r-1][c-1] ("0" indexé) à chaque fois.

Pour cette question, j'avais fait peu de tentatives infructueuses et chaque fois, je continuais à trouver un nouveau cas. J'ai éliminé quelques cas dans lesquels la solution n'est pas possible en utilisant goto et le laisse aller jusqu'au bout où l'impression est impossible. Une solution très simple (on peut en effet dire une solution de force brute puisque j'ai codé pour différents cas possibles individuellement). Ceci est un éditorial pour mon code. Et sur github .

3
nomorequestions

J'ai séparé cela en deux cas spéciaux initiaux, puis j'ai eu un algorithme général. La version tl; dr consiste à construire un carré d'espaces vides en haut à gauche. Semblable à d'autres réponses, mais avec moins de cas spéciaux.

Cas spéciaux

Cas 1

Seulement 1 espace vide. Cliquez simplement dans le coin en haut à gauche et terminez.

Cas 2

2 ou 3 espaces vides, avec une grille qui n'est ni Rx1 ni 1xC. C'est impossible, alors nous échouons tôt.

Algorithme

Toujours cliquer dans le coin supérieur gauche. Commencez par un carré vide de 2x2 en haut à gauche (nous avons au moins 4 espaces vides). Nous devons maintenant ajouter les blancs restants. Nous développons ensuite le carré le long d'un bord puis de l'autre, jusqu'à ce que nous n'ayons plus d'espaces vides.

Exemple de commande de masquage:

C  2  6 12
1  3  7 13
4  5  8 14
9 10 11 15

Cas impossible

Notez que lorsque vous commencez un nouvel Edge, vous devez placer au moins deux espaces vides pour que celui-ci soit valide. Donc, si nous n’avons qu’un seul blanc à placer, il doit alors être invalide (à moins que notre Edge n’ait une longueur égale à un). Ma logique ressemblait à ceci:

if maxEdgeLength > 1 and remainingBlanks == 1:
    print('Impossible')
    return

Cependant, nous aurions pu laisser la fin du dernier Edge, ce qui nous donnerait deux blancs maintenant. Bien sûr, nous ne pouvons laisser le dernier blanc que si le dernier Edge avait plus de 2 blancs!

Ma logique pour ce cas spécial ressemblait à ceci:

if remainingBlanks == 1 and lastEdgeSize > 2:
    mineMatrix[lastBlank] = '*'
    blanks += 1
2
William

J'ai utilisé la recherche avec retour arrière, mais je ne pouvais résoudre que la petite entrée.

En gros, l’algorithme commence avec le tableau plein de mines et essaie d’enlever les mines de manière à ce que le premier "clic" résolve le tableau. Le problème est que, pour permettre à un "clic" de se développer dans une autre cellule, l'expansion proviendra d'une autre cellule qui doit également effacer toutes les autres cellules voisines. Parfois, pour s’étendre à une autre cellule, il faudrait enlever d’autres mines et se retrouver avec moins de mines que nécessaire. L'algorithme effectuera un retour en arrière s'il atteint une telle position.

Peut-être est-il plus simple de faire le contraire. Commencez avec un tableau vide et ajoutez chaque mine de manière à ne pas empêcher "l'expansion" du clic initial.

Le code Python complet est ci-dessous:

directions = [
    [-1, -1], [-1, 0], [-1, 1],
    [0, -1],           [0, 1],
    [1,  -1],  [1, 0],  [1, 1],
]

def solve(R, C, M):
    def neighbors(i, j):
        for di, dj in directions:
            if 0 <= (i + di) < R and 0 <= (j + dj) < C:
                yield (i + di, j + dj)

    def neighbors_to_clear(i, j, board):
        return [(ni, nj) for ni, nj in neighbors(i, j) if board[ni][nj] == "*"]

    def clear_board(order):
        to_clear = R * C - M - 1
        board = [["*" for _ in range(C)] for _ in range(R)]
        for i, j in order:
            board[i][j] = "."
            for ni, nj in neighbors_to_clear(i, j, board):
                to_clear -= 1
                board[ni][nj] = "."
        return board, to_clear

    def search(ci, cj):
        nodes = []
        board = []
        to_clear = 1
        nodes.append((ci, cj, []))
        while nodes and to_clear > 0:
            i, j, order = nodes.pop()
            board, to_clear = clear_board(order)
            neworder = order + [(i, j)]
            if to_clear == 0:
                board[ci][cj] = "c"
                return board
            Elif to_clear > 0:
                for ni, nj in neighbors_to_clear(i, j, board):
                    board[ni][nj] = "."
                    nodes.append([ni, nj, neworder])

    for i in range(R):
        for j in range(C):
            board = search(i, j)
            if board:
                for row in board:
                    print "".join(row)
                return

    print "Impossible"
    return

T = int(raw_input())
for i in range(1, T + 1):
    R, C, M = map(int, raw_input().split(" "))
    print("Case #%d:" % i)
    solve(R, C, M)
2
jbochi

ma stratégie était très semblable à la vôtre et je suis passé à la fois petit et grand. Avez-vous pensé aux cas ci-dessous?

  • R * C - M = 1

  • Il n'y a qu'une seule rangée

  • Il n'y a que deux rangées


J'ai retourné R et C quand R> C.

2
Satachito

Pre-Checks

M = (R * C) - 1

Remplissez la grille avec toutes les mines et mettez le clic n'importe où.

R == 1 || C == 1

Remplissez gauche/droite (ou haut/bas) dans l’ordre: clic, non-mines, mines (ex. c...****).

M == (R * C) - 2 || M == (R * C) - 3

Impossible

Algorithme

J'ai commencé avec une grille "vide" (tous les .s) et j'ai placé le clic dans un coin (je vais utiliser le coin en haut à gauche pour le clic et commencer à remplir de mines en partant de la droite).
Nous utiliserons R1 et C1 comme lignes et colonnes "actuelles".

Bien que nous ayons assez de mines pour remplir une ligne ou une colonne qui, une fois supprimées, ne laisse pas une seule ligne ou colonne à gauche (while((M >= R1 && C1 > 2) || (M >= C1 && R1 > 2))), nous "découpons" la grille (remplissez de mines et réduisez R1 ou C1) en utilisant le côté le plus court et enlever autant de mines. Ainsi, un 4x5 avec 6 mines restantes deviendrait un 4x4 avec 2 mines restantes.

  • Si nous nous retrouvons avec une grille 2 x n, nous aurons soit 0 mines (nous avons terminé), soit 1 mine (impossibilité de gagner).
  • Si nous nous retrouvons avec une grille 3 x 3, nous aurons soit 0 mines (nous avons terminé), 1 mine (continuer ci-dessous), ou 2 mines (Impossible de gagner).
  • Toute autre combinaison est gagnable. Nous vérifions si M == min(R1,C1)-1, le cas échéant, insérons une seule mine ou une seule ligne depuis le bord le plus court, puis remplissons le bord le plus court avec les mines restantes.

Exemple

Je vais montrer l'ordre d'entrée des mines dans la grille avec des chiffres, juste pour aider à la visualisation
R = 7, C = 6, M = 29

c...42
....42
...742
..6742
555542
333332
111111  

Il m'a fallu plusieurs essais pour obtenir mon algorithme correct, mais j'ai écrit le mien dans PHP et j'ai obtenu le correct, petit et grand.

1
Jo.

Mon approche de ce problème était la suivante:

  • Pour une grille 1x1, M doit être égal à zéro sinon c'est impossible
  • Pour une grille Rx1 ou 1xC, nous avons besoin de M <= R * C - 2 (placez 'c' sur la dernière cellule avec une cellule vide à côté)
  • Pour une grille RxC, nous avons besoin de M <= R * C - 4 (placez 'c' sur un coin entouré de 3 cellules vides)

En résumé, c aura toujours des cellules autres que les mines, quoi qu’il en soit, sinon c’est impossible. Cette solution a du sens pour moi, et j’ai vérifié la sortie par rapport à leur échantillon et à de petites entrées, mais cela n’a pas été accepté.

Voici mon code:

import sys

fname = sys.argv[1]

handler = open(fname, "r")
lines = [line.strip() for line in handler]

testcases_count = int(lines.pop(0))

def generate_config(R, C, M):
    mines = M

    config = []
    for row in range(1, R+1):
        if mines >= C:
            if row >= R - 1:
                config.append(''.join(['*' * (C - 2), '.' * 2]))
                mines = mines - C + 2
            else:
                config.append(''.join('*' * C))
                mines = mines - C
        Elif mines > 0:
            if row == R - 1 and mines >= C - 2:
                partial_mines = min(mines, C - 2)
                config.append(''.join(['*' * partial_mines, '.' * (C - partial_mines)]))
                mines = mines - partial_mines
            else:
                config.append(''.join(['*' * mines, '.' * (C - mines)]))
                mines = 0
        else:
            config.append(''.join('.' * C))

    # click the last empty cell
    config[-1] = ''.join([config[-1][:-1], 'c'])

    return config

for case in range(testcases_count):
    R, C, M = map(int, lines.pop(0).split(' '))

    # for a 1x1 grid, M has to be zero
    # for a Rx1 or 1xC grid, we must have M <= # of cells - 2
    # for others, we need at least 4 empty cells
    config_possible = (R == 1 and C == 1 and M==0) or ((R == 1 or C == 1) and M <= R * C - 2) or (R > 1 and C > 1 and M <= R * C - 4)

    config = generate_config(R, C, M) if config_possible else None

    print "Case #%d:" % (case+1)
    if config:
        for line in config: print line
    else:
        print "Impossible"

handler.close()

Cela a bien fonctionné par rapport à leur échantillon sur le site Web et par rapport à la petite contribution fournie, mais il me semble que quelque chose me manque.

Voici la sortie de l'exemple:

Case #1:
Impossible
Case #2:
*
.
c
Case #3:
Impossible
Case #4:
***....
.......
.......
......c
Case #5:
**********
**********
**********
**********
**********
**********
**********
**********
**........
.........c

Mise à jour: En lisant l'éditorial de vinaykumar, je comprends ce qui ne va pas avec ma solution. Règles de base du dragueur de mines que j'aurais dû couvrir, à peu près.

1
Aziz Alfoudari

Code z explicite avec des commentaires. O (r + c)

import Java.util.Scanner;
    public class Minesweeper {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            int n = sc.nextInt();
            for(int j=0;j<n;j++) {
                int r =sc.nextInt(),
                    c = sc.nextInt(),
                    m=sc.nextInt();
                //handling for only one space.
                if(r*c-m==1) {
                    System.out.println("Case #"+(int)(j+1)+":");
                    String a[][] = new String[r][c];
                    completeFill(a,r-1,c-1,"*");
                    printAll(a, r-1, c-1);
                }
                //handling for 2 rows or cols if num of mines - r*c < 2 not possible.
                //missed here the handling of one mine.
                else if(r<2||c<2) {
                    if(((r*c) - m) <2) {
                        System.out.println("Case #"+(int)(j+1)+":");
                        System.out.println("Impossible");
                    }
                    else {
                        System.out.println("Case #"+(int)(j+1)+":");
                        draw(r,c,m);
                    }
                }
                //for all remaining cases r*c - <4 as the click box needs to be zero to propagate
                else if(((r*c) - m) <4) {
                    System.out.println("Case #"+(int)(j+1)+":");
                    System.out.println("Impossible");
                }
                //Edge cases found during execution.
                //row or col =2 and m=1 then not possible.
                //row==3 and col==3 and m==2 not possible.
                else {
                    System.out.println("Case #"+(int)(j+1)+":");
                    if(r==3&&m==2&&c==3|| r==2&&m==1 || c==2&&m==1) {
                        System.out.println("Impossible");
                    }
                    else {
                        draw(r,c,m);
                    }
                }
            }
        }
        /*ALGO : IF m < (r and c) then reduce r or c which ever z max 
         * by two first time then on reduce by factor 1. 
         * Then give the input to filling (squarefill) function which files max one line 
         * with given input. and returns the vals of remaining rows and cols.
         * checking the r,c==2 and r,c==3 Edge cases.
         **/
        public static void draw(int r,int c, int m) {
            String a[][] = new String[r][c];
            int norow=r-1,nocol=c-1;
            completeFill(a,norow,nocol,".");
            int startR=0,startC=0;
            int red = 2;
            norow = r;
            nocol = c;
            int row=r,col=c;
            boolean first = true;
            boolean print =true;
            while(m>0&&norow>0&&nocol>0) {
                if(m<norow&&m<nocol) {
                    if(norow>nocol) {
                        norow=norow-red;
                        //startR = startR + red;
                    }
                    else if(norow<nocol){
                        nocol=nocol-red;
                        //startC = startC + red;
                    }
                    else {
                        if(r>c) {
                            norow=norow-red;
                        }
                        else {
                            nocol=nocol-red;
                        }
                    }
                    red=1;
                }
                else {
                    int[] temp = squareFill(a, norow, nocol, startR, startC, m,row,col,first);
                    norow = temp[0];
                    nocol = temp[1];
                    startR =r- temp[0];
                    startC =c -temp[1];
                    row = temp[3];
                    col = temp[4];
                    m = temp[2];
                    red=2;
                    //System.out.println(norow + " "+ nocol+ " "+m);
                    if(norow==3&&nocol==3&&m==2 || norow==2&&m==1 || nocol==2&&m==1) {
                        print =false;
                        System.out.println("Impossible");
                        break;
                    }
                }
                first = false;
            }
            //rectFill(a, 1, r, 1, c);
            if(print)
                printAll(a, r-1, c-1);
        }
        public static void completeFill(String[][] a,int row,int col,String x) {
            for(int i=0;i<=row;i++) {
                for(int j=0;j<=col;j++) {
                    a[i][j] = x;
                }
            }
            a[row][col] = "c";
        }
        public static void printAll(String[][] a,int row,int col) {
            for(int i=0;i<=row;i++) {
                for(int j=0;j<=col;j++) {
                    System.out.print(a[i][j]);
                }
                System.out.println();
            }
        }
        public static int[] squareFill(String[][] a,int norow,int nocol,int startR,int startC,int m,int r, int c, boolean first) {
            if(norow < nocol) {
                int fil = 1;
                m = m - norow;
                for(int i=startR;i<startR+norow;i++) {
                    for(int j=startC;j<startC+fil;j++) {
                        a[i][j] = "*";
                    }
                }
                nocol= nocol-fil;
                c = nocol;
                norow = r;
            }
            else {
                int fil = 1;
                m = m-nocol;
                for(int i=startR;i<startR+fil;i++) {
                    for(int j=startC;j<startC+nocol;j++) {
                        a[i][j] = "*";
                    }
                }
                norow = norow-fil;
                r= norow;
                nocol = c;
            }
            return new int[] {norow,nocol,m,r,c};
        }
    }
1
Bhargav Krishna

J'ai tenté ma chance dans cette question aussi, mais pour une raison quelconque, je n'ai pas réussi les contrôles.

J'ai pensé qu'il était possible de résoudre (matrice 3x3) s'il y avait moins de mines (rangées * cols-4), car je n'avais besoin que de 4 cellules pour "c" et ses limites en tant que "."

Mes algorithmes suivent:

Solvable? :

  1. Vérifie s'il y a assez de place pour les mines (rows*cols - 4 == maximum mines)
  2. Des exceptions telles que des lignes == 1, des colonnes == 1; alors c'est lignes * cols-2
  3. Conditionnel si possible ou impossible

Solution de construction

  1. Build rows*cols matrix, avec la valeur par défaut nil
  2. Allez à m[0][0] et attribuez 'c'
  3. Définir les environnements m[0][0] avec '.'
  4. Faites une boucle en bas à droite de Matrix et assignez '*' jusqu’à ce que les mines soient terminées, puis assignez '.'
0
hrr

La solution peut être trouvée ici . Contenu de la page ci-dessous.

Il existe plusieurs façons de générer une configuration de mine valide. Dans cette analyse, nous essayons d’énumérer tous les cas possibles et d’essayer de générer une configuration valide pour chaque cas (le cas échéant). Plus tard, après avoir eu un aperçu, nous fournissons un algorithme plus facile à mettre en œuvre pour générer une configuration de mine valide (si existante).

Enumérer tous les cas possibles

Nous commençons par vérifier les cas triviaux:

S'il n'y a qu'une seule cellule vide, nous pouvons simplement remplir toutes les cellules avec des mines, à l'exception de la cellule où vous avez cliqué. Si R = 1 ou C = 1, les mines peuvent être placées respectivement de gauche à droite ou de haut en bas et cliquez sur la cellule la plus à droite ou la plus à gauche. Si le tableau ne se trouve pas dans les deux cas triviaux ci-dessus, cela signifie que le tableau a au moins une taille de 2 x 2. Ensuite, nous pouvons vérifier manuellement que:

Si le nombre de cellules vides est 2 ou 3, il est impossible d'avoir une configuration valide. Si R = 2 ou C = 2, les configurations valides n'existent que si M est pair. Par exemple, si R = 2, C = 7 et M = 5, cela est impossible car M est impair. Cependant, si M = 6, nous pouvons placer les mines sur la partie gauche du tableau et cliquer en bas à droite, comme suit: * .... * ... c Si le tableau n'est pas dans tous les cas ci-dessus, cela signifie que le tableau mesure au moins 3 x 3. Dans ce cas, nous pouvons toujours trouver une configuration de mine valide si le nombre de cellules vides est supérieur à 9. Voici une façon de le faire:

Si le nombre de cellules vides est égal ou supérieur à 3 * C, les mines peuvent être placées ligne par ligne en commençant par le haut. Si le nombre de mines restantes peut remplir entièrement la ligne ou est inférieur à C-2, placez les mines de gauche à droite dans cette ligne. Sinon, le nombre de mines restantes est exactement égal à C-1, placez la dernière mine dans la rangée suivante. Par exemple: ****** ****** *****. **** .. ...... -> * ..... ...... ...... ..... c ..... c Si le nombre de cellules vides est inférieur à 3 * C mais au moins 9, nous remplissons d’abord toutes les lignes avec des mines sauf les 3 dernières. Pour les 3 dernières lignes, nous remplissons les mines restantes colonne par colonne à partir de la colonne de gauche. Si les mines restantes de la dernière colonne sont deux, la dernière mine doit être placée dans la colonne suivante. Par exemple: ****** ****** .... -> * ... ** .... * ..... * .... c * .... c À présent, il ne reste plus que 9 cellules vides situées dans les 3 x 3 cellules carrées situées dans le coin inférieur droit. Dans ce cas, nous pouvons vérifier à la main que si le nombre de cellules vides est 5 ou 7, il est impossible d'avoir une configuration de mine valide. Sinon, nous pouvons coder en dur une configuration valide pour chaque nombre de cellules vides dans ces 3 x 3 cellules carrées.

Soupir ... c'était beaucoup de cas à couvrir! Comment pouvons-nous nous convaincre que lorsque nous codons la solution, nous ne manquons aucun cas de corner?

Approche de force brute

Pour la petite entrée, la taille de la carte est au maximum de 5 x 5. Nous pouvons vérifier toutes les configurations de mines possibles (25 choisir M) et en trouver une valide (en cliquant sur une cellule vide de la configuration pour afficher toutes les autres cellules vides). Pour vérifier si une configuration de mine est valide, nous pouvons exécuter un algorithme de remplissage (ou une simple recherche avec respiration d'abord) à partir de la cellule vide cliquée et vérifier que toutes les autres cellules vides sont accessibles (c'est-à-dire qu'elles se trouvent dans un composant connecté). . Notez que nous devrions également vérifier toutes les positions de clic possibles. Cette approche par force brute est assez rapide pour une petite contribution.

L’approche par force brute peut être utilisée pour vérifier (pour les petites valeurs de R, C, M) s’il existe un faux négatif dans notre stratégie de dénombrement ci-dessus. Un faux négatif est trouvé quand il existe une configuration de mine valide, mais la stratégie de dénombrement ci-dessus donne Impossible. Une fois que nous sommes convaincus que notre stratégie de dénombrement ne produit pas de faux négatif, nous pouvons l’utiliser pour résoudre le problème des entrées volumineuses.

Une approche plus facile à mettre en œuvre

Après avoir manipulé plusieurs configurations de mines valides à l’aide de la stratégie de dénombrement ci-dessus, vous remarquerez peut-être un schéma: dans une configuration de mine valide, le nombre de mines d’une rangée donnée est toujours égal ou supérieur au nombre de mines des rangées situées en dessous, et toutes les mines sont alignées à gauche. Avec cette idée, nous pouvons implémenter un algorithme de retour en arrière plus simple qui place les mines rangées par rangées de haut en bas avec un nombre de mines non croissant au fur et à mesure que nous procédons pour remplir la rangée suivante et Élaguer si la configuration de la rangée actuelle est invalide peut être cochée en cliquant sur la cellule en bas à droite). Ce retour en arrière avec élagage peut traiter jusqu’à 50 x 50 cartes en un temps raisonnable et est plus simple à mettre en œuvre (c’est-à-dire qu’il n’est pas nécessaire d’énumérer les cas difficiles).

Si la durée du concours était plus courte, nous n'aurions peut-être pas assez de temps pour énumérer tous les cas possibles. Dans ce cas, miser sur l'algorithme de retour en arrière (ou tout autre algorithme plus facile à mettre en œuvre) peut être une bonne idée. Trouver de tels algorithmes est un art :).

0
Richard Fung