web-dev-qa-db-fra.com

Algorithme de largage de bombes

J'ai un n x m matrice constituée d’entiers non négatifs. Par exemple:

2 3 4 7 1
1 5 2 6 2
4 3 4 2 1
2 1 2 4 1
3 1 3 4 1
2 1 4 3 2
6 9 1 6 4

"Lâcher une bombe" diminue de un le nombre de la cellule cible et de ses huit voisins, jusqu'à un minimum de zéro.

x x x 
x X x
x x x

Quel est un algorithme qui déterminerait le nombre minimum de bombes nécessaires pour réduire toutes les cellules à zéro?

Option B (vu que je ne suis pas un lecteur attentif)

En fait, la première version du problème n’est pas celle pour laquelle je cherche une réponse. Je n'ai pas lu attentivement toute la tâche, il y a des contraintes supplémentaires, disons:

Qu'en est-il d'un problème simple, lorsque la séquence en ligne ne doit pas augmenter:

8 7 6 6 5 est la séquence d'entrée possible

7 8 5 5 2 _ n'est pas possible car 7 -> 8 croît en séquence.

Peut-être que trouver une solution à un cas "plus facile" aiderait à trouver une solution pour un cas plus difficile.

PS: Je crois que lorsque plusieurs situations identiques nécessitent un minimum de bombes pour dégager la ligne supérieure, nous en choisissons une qui utilise la plupart des bombes du "côté gauche" de la rangée. Encore une preuve qui pourrait être correcte?

212
abc

Il existe un moyen de réduire cela à un simple sous-problème.

L'explication comprend deux parties: l'algorithme et la raison pour laquelle l'algorithme fournit une solution optimale. Le premier n'aura pas de sens sans le second, je vais donc commencer par le pourquoi.

Si vous envisagez de bombarder le rectangle (supposons un grand rectangle - pas de cas Edge pour le moment), vous pouvez voir que le seul moyen de réduire le rectangle creux de carrés du périmètre à 0 consiste à bombarder le périmètre ou à bombarder le rectangle creux de carrés juste à l'intérieur du périmètre. J'appellerai la couche de périmètre 1 et le rectangle à l'intérieur de la couche 2.

Il est important de noter qu’il est inutile de bombarder la couche 1, car le "rayon de souffle" obtenu est toujours contenu dans le rayon de souffle d’un autre carré de la couche 2. Vous devriez pouvoir vous en convaincre facilement.

Donc, nous pouvons réduire le problème à la recherche d’un moyen optimal de bombarder le périmètre, puis le répéter jusqu’à ce que tous les carrés soient nuls.

Bien sûr, cela ne trouvera pas toujours la solution optimale s'il est possible de bombarder le périmètre de manière moins qu'optimale, mais l'utilisation de X bombes supplémentaires simplifie le problème de la réduction de la couche interne de> X bombes. Ainsi, si nous appelons la couche de permitation un, si nous plaçons X bombes supplémentaires quelque part dans la couche 2 (juste à l'intérieur de la couche 1), pouvons-nous réduire l'effort de bombardement ultérieur de la couche 2 de plus de X? En d'autres termes, nous devons prouver que nous pouvons être avides de réduire le périmètre extérieur.

Mais nous savons que nous pouvons être gourmands. Parce qu'aucune bombe de la couche 2 ne peut jamais être plus efficace pour réduire la couche 2 à 0 qu'une bombe placée de manière stratégique dans la couche 3. Et pour la même raison qu'auparavant - il y a toujours une bombe que nous pouvons placer dans la couche 3 qui affectera chaque carré de la couche 2 qu'une bombe placée dans la couche 2 peut. Donc, il ne peut jamais nous nuire d'être avide (dans ce sens d'avidité).

Il suffit donc de trouver le moyen optimal de réduire le permiter à 0 en bombardant la couche interne suivante.

Nous ne sommes jamais blessés par le premier bombardement du coin à 0, car seul le coin de la couche interne peut l'atteindre, nous n'avons donc vraiment pas le choix (et toute bombe sur le périmètre qui peut atteindre le coin a un rayon de souffle contenu dans le rayon de souffle du coin de la couche interne).

Une fois que nous l’avons fait, les carrés du périmètre adjacent au coin 0 ne peuvent être atteints que par 2 carrés de la couche interne:

0       A       B

C       X       Y

D       Z

À ce stade, le périmètre est effectivement une boucle fermée à 1 dimension, car toute bombe réduira 3 carrés adjacents. Sauf quelques bizarreries près des coins - X peut "toucher" A, B, C et D.

Maintenant, nous ne pouvons utiliser aucune astuce de rayon de souffle - la situation de chaque carré est symétrique, à l'exception des coins étranges, et même là, aucun rayon de souffle n'est un sous-ensemble des autres. Notez que s’il s’agissait d’une ligne (comme le dit le colonel Panic) au lieu d’une boucle fermée, la solution est triviale. Les points finaux doivent être réduits à 0 et il n’est jamais nuisible de bombarder les points adjacents aux points finaux, encore une fois parce que le rayon de souffle est un sur-ensemble. Une fois que vous avez défini votre point final 0, vous avez toujours un nouveau point final. Répétez l'opération (jusqu'à ce que la ligne soit entièrement à 0).

Donc, si nous pouvons réduire de manière optimale un carré de la couche à 0, nous avons un algorithme (car nous avons coupé la boucle et avons maintenant une ligne droite avec des extrémités). Je pense qu'un bombardement adjacent à la case avec la valeur la plus basse (vous donnant 2 options) tel que la plus haute valeur dans les 2 cases de cette valeur la plus basse soit le minimum possible (vous devrez peut-être diviser votre bombardement pour la gérer) sera optimal mais je Je n'ai pas (encore?) de preuve.

38
psr

Pólya dit "Si vous ne pouvez pas résoudre un problème, vous pouvez résoudre un problème plus facile: trouvez-le."

Le problème évident le plus simple est le problème à 1 dimension (lorsque la grille est une seule ligne). Commençons par l'algorithme le plus simple - bombarder avec avidité la plus grande cible. Quand est-ce que ça va mal?

Donné 1 1 1, l’algorithme glouton est indifférent à la cellule qu’elle bombarde en premier. Bien sûr, la cellule centrale est meilleure - elle remet les trois cellules à la fois. Ceci suggère un nouvel algorithme A, "bombe pour minimiser la somme restante". Quand cet algorithme va-t-il mal?

Donné 1 1 2 1 1, l'algorithme A est indifférent entre le bombardement de la 2e, 3e ou 4e cellules. Mais bombarder la 2e cellule pour quitter 0 0 1 1 1 vaut mieux que de bombarder la 3e cellule pour quitter 1 0 1 0 1. Comment résoudre ce problème? Le problème avec le bombardement de la 3ème cellule est que cela nous laisse travailler à gauche et à droite, ce qui doit être fait séparément.

Que diriez-vous de "bombe pour minimiser la somme restante, mais maximisez le minimum à gauche (de l'endroit où nous avons bombardé) plus le minimum à droite". Appelez cet algorithme B. Quand cet algorithme va-t-il mal?


Edit: Après avoir lu les commentaires, je suis d’accord sur un problème beaucoup plus intéressant: le problème unidimensionnel qui a été modifié pour que les extrémités se rejoignent. J'aimerais voir des progrès à ce sujet.

25
Colonel Panic

Je n'ai eu qu'une solution partielle, car mon temps était écoulé, mais j'espère que même cette solution partielle permet de mieux comprendre une solution possible à ce problème.

Lorsque je suis confronté à un problème difficile, j’aime poser des problèmes plus simples pour développer une intuition sur l’espace du problème. Ici, la première chose à faire était de réduire ce problème 2D à un problème 1D. Considérons une ligne:

0 4 2 1 3 0 1

D'une manière ou d'une autre, vous savez que vous devrez bombarder 4 fois le point 4 Pour le ramener à 0. Comme il reste un nombre inférieur à l'endroit, il est inutile de bombarder le 0 Ou le 4 Après avoir bombardé le 2. En fait, je crois (mais il manque une preuve rigoureuse) que bombarder le 2 Jusqu'à ce que le repère 4 Soit réduit à 0 est au moins aussi efficace que toute autre stratégie pour obtenir ce 4 jusqu’à 0. On peut procéder de gauche à droite dans une stratégie comme celle-ci:

index = 1
while index < line_length
  while number_at_index(index - 1) > 0
    bomb(index)
  end
  index++
end
# take care of the end of the line
while number_at_index(index - 1) > 0
  bomb(index - 1)
end

Quelques exemples de commandes de bombardement:

0 4[2]1 3 0 1
0 3[1]0 3 0 1
0 2[0]0 3 0 1
0 1[0]0 3 0 1
0 0 0 0 3[0]1
0 0 0 0 2[0]0
0 0 0 0 1[0]0
0 0 0 0 0 0 0

4[2]1 3 2 1 5
3[1]0 3 2 1 5
2[0]0 3 2 1 5
1[0]0 3 2 1 5
0 0 0 3[2]1 5
0 0 0 2[1]0 5
0 0 0 1[0]0 5
0 0 0 0 0 0[5]
0 0 0 0 0 0[4]
0 0 0 0 0 0[3]
0 0 0 0 0 0[2]
0 0 0 0 0 0[1]
0 0 0 0 0 0 0

L’idée de commencer par un nombre qui doit diminuer d’une manière ou d’une autre est intéressante, car il devient soudain possible de trouver une solution qui, comme certains, prétendent être au moins aussi bon comme toutes les autres solutions.

La prochaine étape dans la complexité où cette recherche de au moins aussi bonne est encore possible est à la limite du tableau. Il est clair pour moi qu'il n'y a jamais d'avantage strict à bombarder le bord extérieur; vous feriez mieux de bombarder l'endroit et d'obtenir trois autres espaces gratuitement. Compte tenu de cela, nous pouvons dire que bombarder l'anneau 1 à l'intérieur de Edge est au moins aussi bon que de bombarder Edge. De plus, nous pouvons combiner cela avec l’intuition que bombarder le bon à l’intérieur de l’Edge est en fait le seul moyen de réduire les espaces Edge à 0. De plus, il est trivialement simple de déterminer la stratégie optimale (en ce sens moins efficace que toute autre stratégie) pour ramener les chiffres de coin à 0. Nous regroupons tout cela et pouvons nous rapprocher beaucoup plus d'une solution dans l'espace 2D.

Étant donné l’observation concernant les pièces de coin, nous pouvons affirmer avec certitude que nous connaissons la stratégie optimale pour passer d’un tableau de départ à un tableau avec des zéros à tous les coins. C'est un exemple d'un tel tableau (j'ai emprunté les nombres des deux tableaux linéaires ci-dessus). J'ai étiqueté certains espaces différemment, et je vais expliquer pourquoi.

0 4 2 1 3 0 1 0
4 x x x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

On remarquera à la rangée du haut que ressemble beaucoup à l'exemple linéaire que nous avons vu précédemment. Nous rappelons notre observation précédente selon laquelle le moyen optimal de ramener la rangée supérieure à 0 consiste à bombarder la deuxième rangée (la rangée x.). Il n’ya aucun moyen de vider la rangée supérieure en bombardant l’une des rangées y et aucun avantage supplémentaire à bombarder la rangée supérieure en bombardant l’espace correspondant sur la rangée x.

Nous pourrions appliquer la stratégie linéaire à partir du haut (bombarder les espaces correspondants sur la rangée x), nous concernant seulement avec la rangée du haut et rien d’autre. Cela donnerait quelque chose comme ça:

0 4 2 1 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 3 1 0 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 2 0 0 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 1 0 0 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 0 0 0 3 0 1 0
4 x x x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

La faille de cette approche devient très évidente lors des deux derniers attentats. Il est clair que les seuls sites de bombe qui réduisent le chiffre 4 Dans la première colonne de la deuxième ligne sont le premier x et le y. Les deux derniers bombardements sont clairement inférieurs au bombardement du premier x, qui aurait fait exactement la même chose (en ce qui concerne le premier emplacement de la rangée du haut, que nous n'avons pas d'autre moyen de dégager). Puisque nous avons démontré que notre stratégie actuelle est sous-optimale, une modification de stratégie est clairement nécessaire.

À ce stade, je peux faire un pas en arrière dans la complexité et me concentrer sur un seul coin. Considérons celui-ci:

0 4 2 1
4 x y a
2 z . .
1 b . .

Il est clair que la seule façon de réduire à zéro les espaces avec 4 Consiste à bombarder une combinaison de x, y et de z. Avec quelques acrobaties dans mon esprit, je suis à peu près sûr que la solution optimale consiste à bombarder x trois fois, puis a puis b. Maintenant, il s’agit de déterminer comment j’ai trouvé cette solution et si elle révèle une intuition, nous pourrons même résoudre ce problème local. Je remarque qu'il n'y a pas de bombardement des espaces y et z. Tenter de trouver un coin où bombarder ces espaces a du sens donne un coin qui ressemble à ceci:

0 4 2 5 0
4 x y a .
2 z . . .
5 b . . .
0 . . . .

Pour celui-ci, il est clair pour moi que la solution optimale consiste à bombarder y 5 fois et z 5 fois. Allons un peu plus loin.

0 4 2 5 6 0 0
4 x y a . . .
2 z . . . . .
5 b . . . . .
6 . . . . . .
0 . . . . . .
0 . . . . . .

Ici, il est tout aussi intuitif de penser que la solution optimale consiste à bombarder a et b 6 fois, puis x 4 fois.

Il s’agit maintenant de transformer ces intuitions en principes sur lesquels nous pouvons nous appuyer.

Espérons que ça continue!

12
Steven

Pour la question mise à jour, un simple algorithme glouton donne un résultat optimal.

Déposez A [0,0] bombes dans la cellule A [1,1], puis déposez A [1,0] bombes dans la cellule A [2,1] et poursuivez le processus à la baisse. Pour nettoyer le coin inférieur gauche, déposez max (A [N-1,0], A [N-2,0], A [N-3,0]) bombes dans la cellule A [N-2,1]. Cela nettoiera complètement les 3 premières colonnes.

Avec la même approche, nettoyez les colonnes 3,4,5, puis les colonnes 6,7,8, etc.

Malheureusement, cela n'aide pas à trouver une solution au problème initial.


Le problème "plus grand" (sans contrainte "non-croissante") peut être NP-difficile. Voici un croquis d'une preuve.

Supposons que nous ayons un graphe planaire de degré jusqu’à 3. Trouvons le minimum couverture de sommet pour ce graphe. Selon l'article de Wikipedia, ce problème est NP-difficile pour les graphes plans de degré inférieur ou égal à 3. Cela peut être prouvé par réduction de Planar 3SAT. Et la dureté de Planar 3SAT - par réduction de 3SAT. Ces deux preuves sont présentées dans des conférences récentes en "Algorithmic Lower Bounds" par le prof. Erik Demaine (conférences 7 et 9).

Si nous scindons certaines arêtes du graphe d'origine (graphe de gauche sur le diagramme), chacune avec un nombre pair de nœuds supplémentaires, le graphe obtenu (graphe de droite sur le diagramme) doit avoir exactement la même couverture de sommet minimale pour les sommets d'origine. Une telle transformation permet d’aligner les sommets des graphes sur des positions arbitraires de la grille.

enter image description here

Si nous plaçons les sommets des graphes uniquement sur des lignes et des colonnes paires (de manière à ce que deux arêtes incidentes sur un sommet ne forment pas un angle aigu), insérons des "uns" partout où il y a un bord et des "zéros" aux autres positions de la grille, nous pourrions utiliser n’importe quelle solution au problème initial pour trouver la couverture de vertex minimale.

10
Evgeny Kluev

Vous pouvez représenter ce problème sous la forme programmation par nombres entiers problème. (ce n'est qu'une des solutions possibles pour aborder ce problème)

Avoir des points:

a b c d
e f g h
i j k l
m n o p

on peut écrire 16 équations où pour le point f, par exemple, est valable

f <= ai + bi + ci + ei + fi + gi + ii + ji + ki   

minimiser la somme de tous les index et solution entière.

La solution est bien sûr la somme de ces indices.

Cela peut être encore simplifié en définissant tous les xi sur les limites 0, vous obtenez donc l'équation 4 + 1 dans cet exemple.

Le problème est qu’il n’ya pas d’algorithme trivial pour résoudre de tels problèmes. Je ne suis pas expert en la matière, mais résoudre ce problème en programmation linéaire est NP difficile.

9
Luka Rahne

Ceci est une réponse partielle, j'essaie de trouver une limite inférieure et une limite supérieure qui pourraient être le nombre possible de bombes.

Dans les cartes 3x3 et inférieures, la solution est toujours la cellule numérotée la plus grande.

Dans les tableaux supérieurs à 4x4, la première limite inférieure évidente est la somme des coins:

*2* 3  7 *1*
 1  5  6  2
 2  1  3  2
*6* 9  6 *4*

quelle que soit la disposition de la bombe, il est impossible de nettoyer cette carte 4x4 avec moins de 2 + 1 + 6 + 4 = 13 bombes.

Il a été mentionné dans d'autres réponses que placer la bombe dans le coin opposé pour éliminer le coin n'était jamais pire que de placer la bombe sur le coin lui-même.

*2* 3  4  7 *1*
 1  5  2  6  2
 4  3  4  2  1
 2  1  2  4  1
 3  1  3  4  1
 2  1  4  3  2
*6* 9  1  6 *4*

Nous pouvons redresser les angles en plaçant des bombes sur le second pour donner une nouvelle planche:

 0  1  1  6  0
 0  3  0  5  1
 2  1  1  1  0
 2  1  2  4  1
 0  0  0  0  0
 0  0  0  0  0
 0  3  0  2  0

Jusqu'ici tout va bien. Nous avons besoin de 13 bombes pour nettoyer les coins.

Observez maintenant les numéros 6, 4, 3 et 2 indiqués ci-dessous:

 0  1  1 *6* 0
 0  3  0  5  1
 2  1  1  1  0
*2* 1  2 *4* 1
 0  0  0  0  0
 0  0  0  0  0
 0 *3* 0  2  0

Il n’existe aucun moyen de bombarder aucun des deux de ces cellules utilisant une seule bombe, la bombe minimale a donc augmenté de 6 + 4 + 3 + 2, ce qui augmente le nombre de bombes que nous avons utilisées pour éliminer le nous obtenons que le nombre minimum de bombes requis pour cette carte est devenu 28 bombes. Il est impossible d'effacer cette carte avec moins de 28 bombes, c'est la limite inférieure de cette carte.

Vous pouvez utiliser un algorithme glouton pour établir une limite supérieure. D'autres réponses ont montré qu'un algorithme glouton produit une solution utilisant 28 bombes. Comme nous avons prouvé plus tôt qu'aucune solution optimale ne peut avoir moins de 28 bombes, 28 bombes est donc une solution optimale.

Lorsque gourmand et que la méthode pour trouver la borne minimale que j'ai mentionnée ci-dessus ne converge pas, je suppose que vous devez revenir à la vérification de toutes les combinaisons.

L'algorithme permettant de trouver la limite inférieure est le suivant:

  1. Choisissez un élément avec le plus grand nombre, nommez-le P.
  2. Marquez toutes les cellules à deux pas de P et P lui-même comme impossibles à choisir.
  3. Ajoutez P à la liste minimums.
  4. Répétez l'opération jusqu'à l'étape 1 jusqu'à ce que toutes les cellules ne soient plus sélectionnables.
  5. Faites la somme de la liste minimums pour obtenir la limite inférieure.
9
Lie Ryan

Ce serait une approche gourmande:

  1. Calculez une matrice "score" d'ordre n X m, où score [i] [j] est la déduction totale des points de la matrice si la position (i, j) est bombardée. (Le score maximum d'un point est 9 et le score minimum est 0)

  2. En vous déplaçant par rangée, trouvez et choisissez la première position avec le score le plus élevé (disons (i, j)).

  3. Bombe (i, j). Augmenter le nombre de bombes.

  4. Si tous les éléments de la matrice d'origine ne sont pas nuls, passez à l'étape 1.

J'ai des doutes que c'est la solution optimale cependant.

Edit:

L’approche Greedy que j’ai affichée ci-dessus, bien que cela fonctionne, ne nous donne probablement pas la solution optimale. J'ai donc pensé qu'il faudrait y ajouter quelques éléments de DP.

Je pense que nous pouvons convenir qu’à tout moment, l’une des positions avec le "score" le plus élevé (score [i] [j] = déduction totale des points si (i, j) est bombardé) doit être ciblée. À partir de cette hypothèse, voici la nouvelle approche:

NumOfBombs (M): (retourne le nombre minimum d'attentats requis)

  1. Soit une matrice M d'ordre n X m. Si tous les éléments de M sont égaux à zéro, alors retourne 0.

  2. Calculer la matrice "score" M.

    Soit les k positions distinctes P1, P2, ... Pk (1 <= k <= n * m), soient les positions dans M avec les scores les plus élevés.

  3. return (1 + min (NumOfBombs (M1), NumOfBombs (M2), ..., NumOfBombs (Mk)))

    où M1, M2, ..., Mk sont les matrices résultantes si nous bombardons les positions P1, P2, ..., Pk respectivement.

De plus, si nous voulons que l’ordre des positions soit modifié en plus de cela, nous devrons garder une trace des résultats de "min".

9
SidR

Votre nouvea problème, avec les valeurs non décroissantes sur les lignes, est assez facile à résoudre.

Notez que la colonne de gauche contient les nombres les plus élevés. Par conséquent, toute solution optimale doit d’abord réduire cette colonne à zéro. Ainsi, nous pouvons effectuer un bombardement de 1-D sur cette colonne, en réduisant tous ses éléments à zéro. Nous laissons les bombes tomber sur la deuxième colonne afin qu’elles causent le maximum de dégâts. Je pense que de nombreux articles traitent du cas 1D, alors je me sens en sécurité de le laisser passer. (Si vous voulez que je le décrive, je peux.) En raison de la propriété décroissante, les trois colonnes les plus à gauche seront toutes réduites à zéro. Mais nous utiliserons ici un nombre minimum de bombes car la colonne de gauche doit être mise à zéro.

Maintenant, une fois que la colonne de gauche est mise à zéro, nous supprimons simplement les trois colonnes les plus à gauche qui sont maintenant mises à zéro et nous répétons avec la matrice maintenant réduite. Cela doit nous donner une solution optimale car à chaque étape, nous utilisons un nombre minimum prouvable de bombes.

8
nneonneo

Programmation linéaire en nombres entiers Mathematica à l’aide de branches

Comme il a déjà été mentionné, ce problème peut être résolu en utilisant la programmation linéaire en nombres entiers (qui est NP-Hard ). Mathematica a déjà intégré ILP. "To solve an integer linear programming problem Mathematica first solves the equational constraints, reducing the problem to one containing inequality constraints only. Then it uses lattice reduction techniques to put the inequality system in a simpler form. Finally, it solves the simplified optimization problem using a branch-and-bound method." [voir Optimisation sous contrainte Didacticiel de Mathematica ..]

J'ai écrit le code suivant qui utilise les bibliothèques ILP de Mathematica. C'est étonnamment rapide.

solveMatrixBombProblem[problem_, r_, c_] := 
 Module[{}, 
  bombEffect[x_, y_, m_, n_] := 
   Table[If[(i == x || i == x - 1 || i == x + 1) && (j == y || 
        j == y - 1 || j == y + 1), 1, 0], {i, 1, m}, {j, 1, n}];
  bombMatrix[m_, n_] := 
   Transpose[
    Table[Table[
      Part[bombEffect[(i - Mod[i, n])/n + 1, Mod[i, n] + 1, m, 
        n], (j - Mod[j, n])/n + 1, Mod[j, n] + 1], {j, 0, 
       m*n - 1}], {i, 0, m*n - 1}]];
  X := x /@ Range[c*r];
  sol = Minimize[{Total[X], 
     And @@ Thread[bombMatrix[r, c].X >= problem] && 
      And @@ Thread[X >= 0] && Total[X] <= 10^100 && 
      Element[X, Integers]}, X];
  Print["Minimum required bombs = ", sol[[1]]];
  Print["A possible solution = ", 
   MatrixForm[
    Table[x[c*i + j + 1] /. sol[[2]], {i, 0, r - 1}, {j, 0, 
      c - 1}]]];]

Pour l'exemple fourni dans le problème:

solveMatrixBombProblem[{2, 3, 4, 7, 1, 1, 5, 2, 6, 2, 4, 3, 4, 2, 1, 2, 1, 2, 4, 1, 3, 1, 3, 4, 1, 2, 1, 4, 3, 2, 6, 9, 1, 6, 4}, 7, 5]

Les sorties

enter image description here

Pour tous ceux qui lisent ceci avec un algorithme glouton

Essayez votre code sur le problème 10x10 suivant:

5   20  7   1   9   8   19  16  11  3  
17  8   15  17  12  4   5   16  8   18  
4   19  12  11  9   7   4   15  14  6  
17  20  4   9   19  8   17  2   10  8  
3   9   10  13  8   9   12  12  6   18  
16  16  2   10  7   12  17  11  4   15  
11  1   15  1   5   11  3   12  8   3  
7   11  16  19  17  11  20  2   5   19  
5   18  2   17  7   14  19  11  1   6  
13  20  8   4   15  10  19  5   11  12

Ici, il est séparé par une virgule:

5, 20, 7, 1, 9, 8, 19, 16, 11, 3, 17, 8, 15, 17, 12, 4, 5, 16, 8, 18, 4, 19, 12, 11, 9, 7, 4, 15, 14, 6, 17, 20, 4, 9, 19, 8, 17, 2, 10, 8, 3, 9, 10, 13, 8, 9, 12, 12, 6, 18, 16, 16, 2, 10, 7, 12, 17, 11, 4, 15, 11, 1, 15, 1, 5, 11, 3, 12, 8, 3, 7, 11, 16, 19, 17, 11, 20, 2, 5, 19, 5, 18, 2, 17, 7, 14, 19, 11, 1, 6, 13, 20, 8, 4, 15, 10, 19, 5, 11, 12

Pour ce problème, ma solution contient 208 bombes. Voici une solution possible (j'ai pu résoudre ce problème en environ 12 secondes).

enter image description here

Pour tester les résultats obtenus par Mathematica, voyez si votre algorithme glouton peut faire mieux.

3
darksky

Cette solution gourmande semble être correct:

Comme indiqué dans les commentaires, il va échouer en 2D. Mais peut-être pourriez-vous l'améliorer.

Pour 1D:
S'il y a au moins 2 chiffres, vous n'avez pas besoin de tirer à l'extrême gauche, car tirer à la seconde ce n'est pas pire. Alors tirez à la seconde, alors que le premier n'est pas 0, car vous devez le faire. Passer à la cellule suivante. N'oubliez pas la dernière cellule.

Code C++:

void bombs(vector<int>& v, int i, int n){
    ans += n;
    v[i] -= n;
    if(i > 0)
        v[i - 1] -= n;
    if(i + 1< v.size())
        v[i + 1] -= n;
}

void solve(vector<int> v){
    int n = v.size();
    for(int i = 0; i < n;++i){
        if(i != n - 1){
            bombs(v, i + 1, v[i]);
        }
        else
            bombs(v, i, v[i])
    }
}

Donc pour la 2D:
Encore une fois: vous n’avez pas besoin de tirer dans la première rangée (s’il ya la deuxième). Alors tirez vers le second. Résoudre la tâche 1D pour la première rangée. (parce que vous devez le rendre nul). Descendre. N'oubliez pas la dernière ligne.

3
RiaD

Cela permet une recherche approfondie du chemin le plus court (une série d'attentats à la bombe) à travers ce "labyrinthe" de positions. Non, je ne peux pas prouver qu'il n'y a pas d'algorithme plus rapide, désolé.

#!/usr/bin/env python

M = ((1,2,3,4),
     (2,3,4,5),
     (5,2,7,4),
     (2,3,5,8))

def eachPossibleMove(m):
  for y in range(1, len(m)-1):
    for x in range(1, len(m[0])-1):
      if (0 == m[y-1][x-1] == m[y-1][x] == m[y-1][x+1] ==
               m[y][x-1]   == m[y][x]   == m[y][x+1] ==
               m[y+1][x-1] == m[y+1][x] == m[y+1][x+1]):
        continue
      yield x, y

def bomb(m, (mx, my)):
  return Tuple(tuple(max(0, m[y][x]-1)
      if mx-1 <= x <= mx+1 and my-1 <= y <= my+1
      else m[y][x]
      for x in range(len(m[y])))
    for y in range(len(m)))

def findFirstSolution(m, path=[]):
#  print path
#  print m
  if sum(map(sum, m)) == 0:  # empty?
    return path
  for move in eachPossibleMove(m):
    return findFirstSolution(bomb(m, move), path + [ move ])

def findShortestSolution(m):
  black = {}
  nextWhite = { m: [] }
  while nextWhite:
    white = nextWhite
    nextWhite = {}
    for position, path in white.iteritems():
      for move in eachPossibleMove(position):
        nextPosition = bomb(position, move)
        nextPath = path + [ move ]
        if sum(map(sum, nextPosition)) == 0:  # empty?
          return nextPath
        if nextPosition in black or nextPosition in white:
          continue  # ignore, found that one before
        nextWhite[nextPosition] = nextPath

def main(argv):
  if argv[1] == 'first':
    print findFirstSolution(M)
  Elif argv[1] == 'shortest':
    print findShortestSolution(M)
  else:
    raise NotImplementedError(argv[1])

if __== '__main__':
  import sys
  sys.exit(main(sys.argv))
3
Alfe

Il semble qu'une approche de programmation linéaire peut être très utile ici.

Soit Pm x n soit la matrice avec les valeurs des positions:

Matrix of positions

Définissons maintenant une matrice de bombes B (x, y)m x n, avec 1 ≤ x ≤ m , 1 ≤ y ≤ n comme ci-dessous

Bomb matrix

de telle sorte que

Values of positions in bomb matrix

Par exemple:

B(3, 3)

Nous cherchons donc une matrice Bm x n = [ bij] cela

  1. Peut être défini comme une somme de matrices de bombes:

    B as a sum of bomb matrices

    ( qij serait alors la quantité de bombes nous tomberions en position pij)

  2. pij - bij ≤ 0 (pour être plus succinct, disons comme P - B ≤ 0 )

En outre, [~ # ~] b [~ # ~] devrait minimiser la somme sum of quantities of bombs.

Nous pouvons aussi écrire [~ # ~] b [~ # ~] comme la matrice laide devant nous:

B as a matrix of sum of quantities

et depuis P - B ≤ 0 (ce qui signifie P ≤ B ) nous avons le système d’inégalité linéaire ci-dessous:

Relationship between number of bombs dropped and values in positions

Être qmn x 1 défini comme

Vector of quantities

pmn x 1 défini comme

Values of P distributed as a vector

Nous pouvons dire que nous avons un système Le système ci-dessous est représenté par le produit des matrices http://latex.codecogs.com/gif.download?S%5Cmathbf%7Bq%7D&space;%5Cge&space;%5Cmathbf%7Bp%7D étant Smn x mn la matrice à inverser pour résoudre le système. Je ne l'ai pas développé moi-même mais je pense qu'il devrait être facile de le faire en code.

Maintenant, nous avons un problème minimum qui peut être déclaré comme

The system we have to solve

Je crois que c'est quelque chose de facile, presque trivial, à résoudre avec quelque chose comme algorithme simplex (il y a ce doc plutôt cool à ce sujet ). Cependant, je ne connais presque pas de programmation linéaire (je vais suivre un cours à ce sujet sur Coursera mais c’est juste pour le futur ...), j’ai eu quelques maux de tête en essayant de le comprendre et j’ai un énorme travail de pigiste à finir juste abandonner ici. Il se peut que j’ai fait quelque chose de mal à un moment donné ou que cela ne puisse pas aller plus loin, mais je crois que ce chemin peut éventuellement conduire à le Solution. Quoi qu'il en soit, je suis impatient de recevoir vos commentaires.

(Merci spécial pour ce site incroyable pour créer des images à partir d'expressions LaTeX )

3
brandizzi

Il n'est pas nécessaire de transformer le problème en sous-problèmes linéaires.

Utilisez plutôt une heuristique gloutonne simple, qui consiste à bombarder les coins, en commençant par le plus grand.

Dans l'exemple donné, il y a quatre coins, {2, 1, 6, 4}. Pour chaque coin, il n'y a pas de meilleure solution que de bombarder la cellule en diagonale jusqu'au coin. Nous savons donc que nos premiers 2 + 1 + 6 + 4 = 13 bombardements doivent avoir lieu dans ces cellules en diagonale. Après le bombardement, nous nous retrouvons avec une nouvelle matrice:

2 3 4 7 1      0 1 1 6 0      0 1 1 6 0     1 1 6 0     0 0 5     0 0 0 
1 5 2 6 2      0 3 0 5 1      0 3 0 5 1  => 1 0 4 0  => 0 0 3  => 0 0 0  
4 3 4 2 1      2 1 1 1 0      2 1 1 1 0     0 0 0 0     0 0 0     0 0 3  
2 1 2 4 1  =>  2 1 2 4 1  =>  2 1 2 4 1     0 0 3 0     0 0 3      
3 1 3 4 1      0 0 0 0 0      0 0 0 0 0 
2 1 4 3 2      0 0 0 0 0      0 0 0 0 0 
6 9 1 6 4      0 3 0 2 0      0 0 0 0 0 

Après les 13 premiers bombardements, nous utilisons l'heuristique pour éliminer 3 0 2 au moyen de trois bombardements. Nous avons maintenant 2 nouveaux coins, {2, 1} au 4ème rang. Nous bombardons ceux-ci, 3 autres bombardements. Nous avons réduit la matrice à 4 x 4 maintenant. Il y a un coin, le coin supérieur gauche. Nous bombardons cela. Il reste 2 coins, {5, 3}. Puisque 5 est le plus grand virage, nous bombardons ce premier bombardement, puis bombardons finalement le 3 dans l’autre coin. Le total est 13 + 3 + 3 + 1 + 5 + 3 = 28.

3
Tyler Durden

Je pense que pour minimiser le nombre de bombes, il suffit simplement de déterminer la zone qui a la force la plus puissante. est plus fort .. et bombarder là-bas .. et jusqu'à ce que le champ soit plat .. pour cette classé la réponse est 28

var oMatrix = [
[2,3,4,7,1],
[1,5,2,6,2],
[4,3,4,2,1],
[2,1,2,4,1],
[3,1,3,4,1],
[2,1,4,3,2],
[6,9,1,6,4]
]

var nBombs = 0;
do
{
    var bSpacesLeftToBomb = false;
    var nHigh = 0;
    var nCellX = 0;
    var nCellY = 0;
    for(var y = 1 ; y<oMatrix.length-1;y++) 
        for(var x = 1 ; x<oMatrix[y].length-1;x++)  
        {
            var nValue = 0;
            for(var yy = y-1;yy<=y+1;yy++)
                for(var xx = x-1;xx<=x+1;xx++)
                    nValue += oMatrix[yy][xx];

            if(nValue>nHigh)
            {
                nHigh = nValue;
                nCellX = x;
                nCellY = y; 
            }

        }
    if(nHigh>0)
    {
        nBombs++;

        for(var yy = nCellY-1;yy<=nCellY+1;yy++)
        {
            for(var xx = nCellX-1;xx<=nCellX+1;xx++)
            {
                if(oMatrix[yy][xx]<=0)
                    continue;
                oMatrix[yy][xx] = --oMatrix[yy][xx];
            }
        }
        bSpacesLeftToBomb = true;
    }
}
while(bSpacesLeftToBomb);

alert(nBombs+'bombs');
2
CaldasGSM

Voici une solution qui généralise les bonnes propriétés des angles.

Supposons que nous puissions trouver un point de chute parfait pour un champ donné, c’est-à-dire le meilleur moyen de diminuer sa valeur. Ensuite, pour trouver le nombre minimum de bombes à larguer, un premier projet d’algorithme pourrait être (le code est copié-collé à partir de Ruby):

dropped_bomb_count = 0
while there_are_cells_with_non_zero_count_left
  coordinates = choose_a_perfect_drop_point
  drop_bomb(coordinates)
  dropped_bomb_count += 1
end
return dropped_bomb_count

Le défi est choose_a_perfect_drop_point. Premièrement, définissons ce qu'est un point de chute parfait.

  • A point de chute pour (x, y) diminue la valeur en (x, y). Cela peut également diminuer les valeurs dans d'autres cellules.
  • Un point de chute a pour (x, y) est meilleur qu'un point de chute b pour (x, y) _ si elle diminue les valeurs dans un sur-ensemble propre des cellules que b diminue.
  • Un point de chute est maximal s'il n'y a pas d'autre meilleur point de chute.
  • Deux points de chute pour (x, y) sont équivalent s’ils diminuent le même ensemble de cellules.
  • Un point de chute pour (x, y) est parfait s'il est équivalent à tous les points de chute maximaux pour (x, y).

S'il y a un point de chute parfait pour (x, y), vous ne pouvez pas diminuer la valeur à (x, y) plus efficacement que de larguer une bombe sur l’un des points de chute parfaits pour (x, y).

Un point de chute parfait pour un champ donné est un point de chute parfait pour l'une de ses cellules.

Voici quelques exemples:

1 0 1 0 0
0 0 0 0 0
1 0 0 0 0
0 0 0 0 0
0 0 0 0 0

Le point de chute idéal pour la cellule (0, 0) (index de base zéro) est (1, 1). Tous les autres points de chute pour (1, 1), C'est (0, 0), (0, 1), et (1, 0), diminuez moins de cellules.

0 0 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
0 0 0 0 0

Un point de chute parfait pour la cellule (2, 2) (index de base zéro) est (2, 2), ainsi que toutes les cellules environnantes (1, 1), (1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2), et (3, 3).

0 0 0 0 1
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
0 0 0 0 0

un point de chute parfait pour la cellule (2, 2) est (3, 1): Diminue la valeur dans (2, 2), et la valeur dans (4, 0). Tous les autres points de chute pour (2, 2) ne sont pas maximales, car elles diminuent d’une cellule à l’autre. Le point de chute idéal pour (2, 2) est également le point de chute idéal pour (4, 0), et c’est le seul point de chute parfait pour le champ. Cela conduit à la solution parfaite pour ce domaine (une bombe larguée).

1 0 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
1 0 0 0 0

Il n’existe pas de point de chute parfait pour (2, 2): Tous les deux (1, 1) et (1, 3) diminuer (2, 2) et une autre cellule (ce sont des points de chute maximaux pour (2, 2)), mais ils ne sont pas équivalents. Toutefois, (1, 1) est un point de chute idéal pour (0, 0), et (1, 3) est un point de chute idéal pour (0, 4).

Avec cette définition des points de chute parfaits et un certain ordre de vérifications, le résultat suivant est obtenu pour l'exemple de la question:

Drop bomb on 1, 1
Drop bomb on 1, 1
Drop bomb on 1, 5
Drop bomb on 1, 5
Drop bomb on 1, 5
Drop bomb on 1, 6
Drop bomb on 1, 2
Drop bomb on 1, 2
Drop bomb on 0, 6
Drop bomb on 0, 6
Drop bomb on 2, 1
Drop bomb on 2, 5
Drop bomb on 2, 5
Drop bomb on 2, 5
Drop bomb on 3, 1
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 4
Drop bomb on 3, 4
Drop bomb on 3, 3
Drop bomb on 3, 3
Drop bomb on 3, 6
Drop bomb on 3, 6
Drop bomb on 3, 6
Drop bomb on 4, 6
28

Cependant, l'algorithme ne fonctionne que s'il existe au moins un point de chute parfait après chaque étape. Il est possible de construire des exemples où il n'y a pas de points de chute parfaits:

0 1 1 0
1 0 0 1
1 0 0 1
0 1 1 0

Dans ces cas, nous pouvons modifier l’algorithme de sorte qu’au lieu d’un point de chute parfait, nous choisissions une coordonnée avec un choix minimal de points de chute maximaux, puis calculons le minimum pour chaque choix. Dans le cas ci-dessus, toutes les cellules avec des valeurs ont deux points de chute maximaux. Par exemple, (0, 1) a les points de chute maximaux (1, 1) et (1, 2). Choisir l'un ou l'autre, puis calculer le minimum conduit à ce résultat:

Drop bomb on 1, 1
Drop bomb on 2, 2
Drop bomb on 1, 2
Drop bomb on 2, 1
2
2
Tammo Freese

Pour minimiser le nombre de bombes, nous devons maximiser les effets de chaque bombe. Pour y parvenir, nous devons sélectionner à chaque étape la meilleure cible. Pour chaque point le additionnant et ses huit voisins - pourrait être utilisé comme une quantité efficace de bombarder ce point. Cela fournira une séquence quasi optimale de bombes.

UPD : Nous devrions également prendre en compte le nombre de zéros, car leur utilisation est inefficace. En fait, le problème est de minimiser le nombre de zéros frappés. Mais nous ne pouvons pas savoir comment une étape nous rapproche de cet objectif. Je suis d'accord avec l'idée que le problème est NP-complet. Je propose une approche gourmande qui donnera une réponse proche du réel.

2
Mikhail

Voici une autre idée:

Commençons par attribuer un poids à chaque espace sur le tableau pour le nombre de nombres qui seraient réduits en y larguant une bombe. Ainsi, si l’espace a un nombre non nul, il obtient un point et si un espace adjacent a un nombre non nul, il obtient un point supplémentaire. Donc, s’il existe une grille de 1 000 sur 1 000, un poids est attribué à chacun des 1 million d’espaces.

Ensuite, triez la liste des espaces en fonction du poids et bombardez celle qui a le poids le plus élevé. Cela en a pour notre argent, pour ainsi dire.

Après cela, mettez à jour le poids de chaque espace dont le poids est affecté par la bombe. Ce sera l'espace que vous avez bombardé, et tout espace immédiatement adjacent, et tout espace immédiatement adjacent à ceux-ci. En d'autres termes, tout espace qui aurait pu voir sa valeur réduite à zéro par le bombardement, ou la valeur d'un espace voisin réduite à zéro.

Ensuite, re-trier les espaces de la liste en fonction du poids. En raison du bombardement de quelques sous-ensembles d'espaces seulement, vous n'aurez pas besoin de recourir à toute la liste, déplacez-les simplement dans la liste.

Bombardez le nouvel espace de poids le plus élevé et répétez la procédure.

Cela garantit que chaque bombardement réduit autant d'espaces que possible (en gros, il frappe aussi peu d'espaces qui sont déjà nuls que possible), de sorte qu'il serait optimal, sauf qu'il peut s'agir de poids égaux. Donc, vous devrez peut-être faire un suivi en cas d'égalité pour le poids maximal. Seule une égalité pour le poids du haut compte, mais pas pour les autres, alors espérons que ce ne sera pas trop en arrière.

Edit: Le contre-exemple de Mysticial ci-dessous montre qu'en réalité, ce n'est pas garanti d'être optimal, quels que soient les égalités de poids. Dans certains cas, réduire le poids autant que possible dans une étape donnée laisse en réalité les bombes restantes trop dispersées pour atteindre une réduction cumulative aussi élevée après la deuxième étape que vous pourriez avoir avec un choix légèrement moins gourmand dans la première étape. J'étais un peu trompé par la notion que les résultats sont insensibles à l'ordre des attentats à la bombe. sont insensibles à l'ordre dans le sens où vous pouvez prendre n'importe quelle série d'attentats à la bombe et les rejouer depuis le début dans un ordre différent pour aboutir au même résultat. planche. Mais il ne s'ensuit pas que vous pouvez considérer chaque bombardement indépendamment. Ou, au moins, chaque bombardement doit être considéré de manière à prendre en compte la manière dont il prépare le tableau pour les bombardements ultérieurs.

2
Tim Goodman

Si vous voulez la solution optimale absolue pour nettoyer la carte, vous devrez utiliser un retour en arrière classique, mais si la matrice est très grande, il faudra du temps pour trouver la meilleure solution. Si vous voulez une solution optimale "possible", vous pouvez utiliser un algorithme glouton. , si vous avez besoin d’aide pour écrire l’algorithme, je peux vous aider

En y réfléchissant, c’est le meilleur moyen. Faites une autre matrice là où vous stockez les points supprimés en lâchant une bombe puis choisissez la cellule avec le maximum de points et déposez la bombe là-bas, mettez à jour la matrice de points et continuez. Exemple:

2 3 5 -> (2+(1*3)) (3+(1*5)) (5+(1*3))
1 3 2 -> (1+(1*4)) (3+(1*7)) (2+(1*4))
1 0 2 -> (1+(1*2)) (0+(1*5)) (2+(1*2))

valeur de cellule +1 pour chaque cellule adjacente avec une valeur supérieure à 0

1
cosmin.danisor
  1. Ne bombardez jamais la frontière (sauf si square n'a pas de voisin non transfrontalier)
  2. Zero corner.
  3. Vers le coin zéro, déposez la valeur du coin un carré plus loin en diagonale (le seul voisin non transfrontalier)
  4. Cela va créer de nouveaux coins. Aller à 2

Edit: je n’ai pas remarqué que Kostek avait suggéré une approche presque identique, c’est pourquoi j’affirme avec plus de conviction que si les coins à nettoyer sont choisis pour rester toujours sur la couche la plus externe, c’est optimal.

Dans l'exemple de l'OP: laisser tomber 2 (comme 1 + 1 ou 2) sur autre chose que sur 5 ne conduit pas à frapper une case qui tomberait sur 5. Il faut donc simplement laisser tomber 2 sur 5 (et 6 en bas à gauche 1 ...)

Après cela, il n’ya plus qu’un moyen pour effacer (en haut à gauche) ce qui était à l’origine 1 (maintenant 0), c’est en laissant tomber 0 sur B3 (Excel comme notation). Etc.

Seulement après avoir effacé les colonnes A et E entières et les rangées 1 et 7, commencez à effacer une couche plus en profondeur.

Considérez effacé uniquement les effacés intentionnellement, effacer les angles de valeur ne coûte rien et simplifie la réflexion.

Parce que toutes les bombes larguées de cette manière doivent être larguées et que cela conduit à des champs nettoyés, c'est la solution optimale.


Après un bon sommeil, j'ai réalisé que ce n'était pas vrai. Considérer

  ABCDE    
1 01000
2 10000
3 00000
4 00000

Mon approche lâcherait des bombes sur B3 et C2, alors que larguer sur B2 serait suffisant

1
Alpedar

J'ai aussi 28 coups. J'ai utilisé deux tests pour déterminer le meilleur coup suivant: tout d'abord, le coup produisant la somme minimale pour le tableau. Deuxièmement, à parts égales, le mouvement produisant la densité maximale, définie comme suit:

number-of-zeros / number-of-groups-of-zeros

C'est Haskell. "résoudre le tableau" montre la solution du moteur. Vous pouvez jouer au jeu en tapant "main", puis entrez un point cible, "meilleur" pour une recommandation ou "quitter" pour quitter.

SORTIE:
* Main> résolveur
[(4,4), (3,6), (3,3), (2,2), (2,2), (4,6), (4,6), (2 , 6), (3,2), (4,2), (2,6), (3,3), (4,3), (2,6), (4,2), (4,6 ), (4,6), (3,6), (2,6), (2,6), (2,4), (2,4), (2,6), (3,6), (4,2), (4,2), (4,2), (4,2)]

import Data.List
import Data.List.Split
import Data.Ord
import Data.Function(on)

board = [2,3,4,7,1,
         1,5,2,6,2,
         4,3,4,2,1,
         2,1,2,4,1,
         3,1,3,4,1,
         2,1,4,3,2,
         6,9,1,6,4]

n = 5
m = 7

updateBoard board pt =
  let x = fst pt
      y = snd pt
      precedingLines = replicate ((y-2) * n) 0
      bomb = concat $ replicate (if y == 1
                                    then 2
                                    else min 3 (m+2-y)) (replicate (x-2) 0 
                                                         ++ (if x == 1 
                                                                then [1,1]
                                                                else replicate (min 3 (n+2-x)) 1)
                                                                ++ replicate (n-(x+1)) 0)
  in zipWith (\a b -> max 0 (a-b)) board (precedingLines ++ bomb ++ repeat 0)

showBoard board = 
  let top = "   " ++ (concat $ map (\x -> show x ++ ".") [1..n]) ++ "\n"
      chunks = chunksOf n board
  in putStrLn (top ++ showBoard' chunks "" 1)
       where showBoard' []     str count = str
             showBoard' (x:xs) str count =
               showBoard' xs (str ++ show count ++ "." ++ show x ++ "\n") (count+1)

instances _ [] = 0
instances x (y:ys)
  | x == y    = 1 + instances x ys
  | otherwise = instances x ys

density a = 
  let numZeros = instances 0 a
      groupsOfZeros = filter (\x -> head x == 0) (group a)
  in if null groupsOfZeros then 0 else numZeros / fromIntegral (length groupsOfZeros)

boardDensity board = sum (map density (chunksOf n board))

moves = [(a,b) | a <- [2..n-1], b <- [2..m-1]]               

bestMove board = 
  let lowestSumMoves = take 1 $ groupBy ((==) `on` snd) 
                              $ sortBy (comparing snd) (map (\x -> (x, sum $ updateBoard board x)) (moves))
  in if null lowestSumMoves
        then (0,0)
        else let lowestSumMoves' = map (\x -> fst x) (head lowestSumMoves) 
             in fst $ head $ reverse $ sortBy (comparing snd) 
                (map (\x -> (x, boardDensity $ updateBoard board x)) (lowestSumMoves'))   

solve board = solve' board [] where
  solve' board result
    | sum board == 0 = result
    | otherwise      = 
        let best = bestMove board 
        in solve' (updateBoard board best) (result ++ [best])

main :: IO ()
main = mainLoop board where
  mainLoop board = do 
    putStrLn ""
    showBoard board
    putStr "Pt: "
    a <- getLine
    case a of 
      "quit"    -> do putStrLn ""
                      return ()
      "best"    -> do putStrLn (show $ bestMove board)
                      mainLoop board
      otherwise -> let ws = splitOn "," a
                       pt = (read (head ws), read (last ws))
                   in do mainLoop (updateBoard board pt)
1

fonction d'évaluation, somme totale:

int f (int ** matrix, int width, int height, int x, int y)
{
    int m[3][3] = { 0 };

    m[1][1] = matrix[x][y];
    if (x > 0) m[0][1] = matrix[x-1][y];
    if (x < width-1) m[2][1] = matrix[x+1][y];

    if (y > 0)
    {
        m[1][0] = matrix[x][y-1];
        if (x > 0) m[0][0] = matrix[x-1][y-1];
        if (x < width-1) m[2][0] = matrix[x+1][y-1];
    }

    if (y < height-1)
    {
        m[1][2] = matrix[x][y+1];
        if (x > 0) m[0][2] = matrix[x-1][y+1];
        if (x < width-1) m[2][2] = matrix[x+1][y+1];
    }

    return m[0][0]+m[0][1]+m[0][2]+m[1][0]+m[1][1]+m[1][2]+m[2][0]+m[2][1]+m[2][2];
}

fonction objective:

Point bestState (int ** matrix, int width, int height)
{
    Point p = new Point(0,0);
    int bestScore = 0;
    int b = 0;

    for (int i=0; i<width; i++)
        for (int j=0; j<height; j++)
        {
            b = f(matrix,width,height,i,j);

            if (b > bestScore)
            {
                bestScore = best;
                p = new Point(i,j);
            }
        }

    retunr p;
}

détruire la fonction:

void destroy (int ** matrix, int width, int height, Point p)
{
    int x = p.x;
    int y = p.y;

    if(matrix[x][y] > 0) matrix[x][y]--;
    if (x > 0) if(matrix[x-1][y] > 0) matrix[x-1][y]--;
    if (x < width-1) if(matrix[x+1][y] > 0) matrix[x+1][y]--;

    if (y > 0)
    {
        if(matrix[x][y-1] > 0) matrix[x][y-1]--;
        if (x > 0) if(matrix[x-1][y-1] > 0) matrix[x-1][y-1]--;
        if (x < width-1) if(matrix[x+1][y-1] > 0) matrix[x+1][y-1]--;
    }

    if (y < height-1)
    {
        if(matrix[x][y] > 0) matrix[x][y+1]--;
        if (x > 0) if(matrix[x-1][y+1] > 0) matrix[x-1][y+1]--;
        if (x < width-1) if(matrix[x+1][y+1] > 0) matrix[x+1][y+1]--;
    }
}

fonction de but:

bool isGoal (int ** matrix, int width, int height)
{
    for (int i=0; i<width; i++)
        for (int j=0; j<height; j++)
            if (matrix[i][j] > 0)
                return false;
    return true;
}

fonction de maximisation linéaire:

void solve (int ** matrix, int width, int height)
{
    while (!isGoal(matrix,width,height))
    {
        destroy(matrix,width,height, bestState(matrix,width,height));
    }
}

Ce n'est pas optimal, mais peut être optimisé en trouvant une meilleure fonction d'évaluation.

..mais en pensant à ce problème, je pensais que l’un des problèmes principaux était d’obtenir des chiffres abandonnés au milieu de zéros à un moment donné, je prendrais donc une autre approche .. qui consiste à dominer les valeurs minimales en zéro, puis autant que possible des zéros d'échappement, ce qui conduit à la minimisation générale de la ou des valeurs existantes minimales

1
Khaled.K

Eh bien, supposons que nous numérotions les positions du tableau 1, 2, ..., n x m. Toute séquence de largages de bombes peut être représentée par une séquence de nombres dans cet ensemble, où les nombres peuvent être répétés. Cependant, l'effet sur le tableau est le même quel que soit l'ordre dans lequel vous déposez les bombes. Vous pouvez donc choisir n'importe quel choix de bombes sous la forme d'une liste de nombres nxm, le premier chiffre représentant le nombre de bombes larguées à la position 1. , le second chiffre représente le nombre de bombes larguées en position 2, etc. Appelons cette liste de nombres nxm la "clé".

Vous pouvez d'abord essayer de calculer tous les états de la carte résultant d'une chute de bombe, puis de les utiliser pour calculer tous les états de la carte résultant de deux largages de bombe, etc. jusqu'à obtenir tous les zéros. Mais à chaque étape, vous mettriez en cache les états en utilisant la clé que j'ai définie ci-dessus, vous pourrez donc utiliser ces résultats pour calculer l'étape suivante (une approche de "programmation dynamique").

Toutefois, en fonction de la taille de n, m et des nombres dans la grille, les besoins en mémoire de cette approche peuvent être excessifs. Vous pouvez jeter tous les résultats de la bombe N une fois que vous avez calculé tous les résultats de la N + 1, ce qui vous permet de réaliser des économies. Et bien sûr, vous ne pouviez rien mettre en cache au prix de l’avoir pris beaucoup plus longtemps - l’approche de programmation dynamique permet à la mémoire d’échanger de la vitesse.

1
Tim Goodman

Force brute !

Je sais que ce n'est pas efficace, mais même si vous trouvez un algorithme plus rapide, vous pouvez toujours tester ce résultat pour savoir s'il est précis.

Utilisez des récursions, comme ceci:

void fn(tableState ts, currentlevel cl)
{
  // first check if ts is all zeros yet, if not:
  //
  // do a for loop to go through all cells of ts, 
  // for each cell do a bomb, and then
  // call: 
  // fn(ts, cl + 1);

}

Vous pouvez rendre ceci plus efficace en mettant en cache, si différentes manières mènent au même résultat, vous ne devriez pas répéter les mêmes étapes.

Élaborer:

si la cellule de bombardement 1,3,5 conduit au même résultat que la cellule de bombardement 5,3,1, vous ne devez pas refaire toutes les étapes suivantes pour les deux cas, un seul suffit, vous devez stocker dans un endroit états de la table et utiliser ses résultats.

Un hachage de statistiques de table peut être utilisé pour effectuer une comparaison rapide.

1
sharp12345

Il semble y avoir une sous-structure correspondante non bipartite ici. Considérez l'instance suivante:

0010000
1000100
0000001
1000000
0000001
1000100
0010000

La solution optimale à ce cas est la taille 5, car elle correspond à la taille d'une couverture minimale des sommets d'un cycle de 9 cycles par ses bords.

Cette affaire, en particulier, montre que la relaxation en programmation linéaire que quelques personnes ont postée n’est pas exacte, ne fonctionne pas et toutes ces mauvaises choses. Je suis presque sûr de pouvoir réduire "le moins possible d'arêtes" à votre réseau, ce qui me laisse douter que l'une des solutions gourmandes/ascendantes ne fonctionne pas.

Je ne vois pas comment résoudre ce problème en temps polynomial dans le pire des cas. Il pourrait y avoir une solution très intelligente de recherche binaire et DP que je ne vois pas.

[~ # ~] modifier [~ # ~] : Je vois que le concours ( http://deadline24.pl ) est agnostique au langage; ils vous envoient un tas de fichiers d’entrée et vous leur envoyez des sorties. Donc, vous n'avez pas besoin de quelque chose qui tourne dans le pire cas polynomial. En particulier, vous obtenez à regardez l'entrée!

Il y a un tas de petits cas dans l'entrée. Ensuite, il y a un cas 10x1000, un cas 100x100 et un cas 1000x1000. Les trois grands cas sont tous très sage. Les entrées horizontalement adjacentes ont généralement la même valeur. Sur une machine relativement lourde, je suis en mesure de résoudre tous les cas en utilisant brusquement CPLEX en quelques minutes. J'ai eu de la chance sur le 1000x1000; la relaxation LP a une solution intégrale optimale. Mes solutions sont en accord avec le .ans fichiers fournis dans le paquet de données de test.

Je parie que vous pouvez utiliser la structure de l'entrée de manière beaucoup plus directe que moi si vous y jetiez un coup d'œil; semble que vous pouvez simplement supprimer la première ligne, ou deux, ou trois à plusieurs reprises jusqu'à ce qu'il ne vous reste plus rien. (On dirait que, dans le 1000x1000, toutes les lignes sont non croissantes? Je suppose que c'est de là que vient votre "partie B"?)

1
tmyklebu

Voici ma solution .. Je ne l'écrirai pas encore dans le code car je n'ai pas le temps, mais je crois que cela devrait produire un nombre optimal de déplacements à chaque fois - bien que je ne sois pas sûr de l'efficacité avec laquelle il serait les points à bombarder.

Premièrement, comme @Luka Rahne l’a déclaré dans l’un des commentaires, l’ordre dans lequel vous bombardez n’est pas important, mais seulement la combinaison.

Deuxièmement, comme beaucoup d’autres l’ont dit, bombarder la diagonale à partir des coins est optimal car il touche plus de points que les coins.

Ceci génère la base de ma version de l'algorithme: Nous pouvons bombarder le '1-off des coins' en premier ou en dernier, peu importe (en théorie) Nous bombardons les premiers parce que cela rend les décisions ultérieures plus faciles (en pratique) Nous bombardons le point qui affecte le plus de points, tout en bombardant simultanément ces coins.

Définissons Points of Resistance comme étant les points du tableau avec le points non bombardables + plus grand nombre de autour d'eux.

points non bombardables peuvent être définis comme des points qui n'existent pas dans notre portée du tableau que nous examinons actuellement.

Je définirai également 4 bornes qui gèreront notre portée: Haut = 0, Gauche = 0, Bas = k, droite = j. (valeurs pour commencer)

Enfin, je définirai bombes optimales comme des bombes larguées sur des points adjacents à des points de résistance et touchant (1) le point de résistance ayant la valeur la plus élevée et (2) le plus grand nombre de points. possible.

En ce qui concerne l'approche, il est évident que nous travaillons de l'extérieur. Nous pourrons travailler avec 4 "bombardiers" en même temps.

Les premiers points de résistance sont évidemment nos coins. Les points "hors limites" ne peuvent pas être bombardés (il y a 5 points en dehors de la portée de chaque coin). Nous bombardons donc les points en diagonale l’un en premier.

Algorithme:

  1. Trouvez les 4 points de bombe optimaux.
  2. Si un point de bombardement bombarde un point de résistance qui touche 2 bornes (c’est-à-dire un coin), bombardez jusqu’à ce point soit 0. Sinon, bombardez chaque point jusqu’à ce que l’un des points de résistance touchant le point optimal soit 0.
  3. pour chaque borne: si (somme (borne) == 0) avance avancée

répéter jusqu'à TOP = BOTTOM et LEFT = RIGHT

Je vais essayer d'écrire le code réel plus tard

1
Etai

Je ne peux pas penser à un moyen de calculer le nombre réel sans calculer simplement la campagne de bombardement en utilisant ma meilleure heuristique et en espérant obtenir un résultat raisonnable.

Donc, ma méthode consiste à calculer une métrique d'efficacité de bombardement pour chaque cellule, bombarder la cellule avec la valeur la plus élevée,… itérer le processus jusqu'à ce que tout soit aplati. Certains ont préconisé l’utilisation de métriques comme dommages potentiels (c’est-à-dire un score compris entre 0 et 9), mais cela n’est pas satisfaisant en martelant des cellules de grande valeur et en évitant le chevauchement des dommages. Je calculerais cell value - sum of all neighbouring cells, réinitialisez tout positif à 0 et utilisez la valeur absolue de tout négatif. Intuitivement, cette métrique doit faire une sélection qui aide à maximiser le chevauchement des dommages sur les cellules avec un nombre élevé au lieu de les marteler directement.

Le code ci-dessous atteint la destruction totale du champ d’essai dans 28 bombes (notez que l’utilisation de dommages potentiels en métrique en donne 31!).

using System;
using System.Collections.Generic;
using System.Linq;

namespace StackOverflow
{
  internal class Program
  {
    // store the battle field as flat array + dimensions
    private static int _width = 5;
    private static int _length = 7;
    private static int[] _field = new int[] {
        2, 3, 4, 7, 1,
        1, 5, 2, 6, 2,
        4, 3, 4, 2, 1,
        2, 1, 2, 4, 1,
        3, 1, 3, 4, 1,
        2, 1, 4, 3, 2,
        6, 9, 1, 6, 4
    };
    // this will store the devastation metric
    private static int[] _metric;

    // do the work
    private static void Main(string[] args)
    {
        int count = 0;

        while (_field.Sum() > 0)
        {
            Console.Out.WriteLine("Round {0}:", ++count);
            GetBlastPotential();
            int cell_to_bomb = FindBestBombingSite();
            PrintField(cell_to_bomb);
            Bomb(cell_to_bomb);
        }
        Console.Out.WriteLine("Done in {0} rounds", count);
    } 

    // convert 2D position to 1D index
    private static int Get1DCoord(int x, int y)
    {
        if ((x < 0) || (y < 0) || (x >= _width) || (y >= _length)) return -1;
        else
        {
            return (y * _width) + x;
        }
    }

    // Convert 1D index to 2D position
    private static void Get2DCoord(int n, out int x, out int y)
    {
        if ((n < 0) || (n >= _field.Length))
        {
            x = -1;
            y = -1;
        }
        else
        {
            x = n % _width;
            y = n / _width;
        }
    }

    // Compute a list of 1D indices for a cell neighbours
    private static List<int> GetNeighbours(int cell)
    {
        List<int> neighbours = new List<int>();
        int x, y;
        Get2DCoord(cell, out x, out y);
        if ((x >= 0) && (y >= 0))
        {
            List<int> tmp = new List<int>();
            tmp.Add(Get1DCoord(x - 1, y - 1));
            tmp.Add(Get1DCoord(x - 1, y));
            tmp.Add(Get1DCoord(x - 1, y + 1));
            tmp.Add(Get1DCoord(x, y - 1));
            tmp.Add(Get1DCoord(x, y + 1));
            tmp.Add(Get1DCoord(x + 1, y - 1));
            tmp.Add(Get1DCoord(x + 1, y));
            tmp.Add(Get1DCoord(x + 1, y + 1));

            // eliminate invalid coords - i.e. stuff past the edges
            foreach (int c in tmp) if (c >= 0) neighbours.Add(c);
        }
        return neighbours;
    }

    // Compute the devastation metric for each cell
    // Represent the Value of the cell minus the sum of all its neighbours
    private static void GetBlastPotential()
    {
        _metric = new int[_field.Length];
        for (int i = 0; i < _field.Length; i++)
        {
            _metric[i] = _field[i];
            List<int> neighbours = GetNeighbours(i);
            if (neighbours != null)
            {
                foreach (int j in neighbours) _metric[i] -= _field[j];
            }
        }
        for (int i = 0; i < _metric.Length; i++)
        {
            _metric[i] = (_metric[i] < 0) ? Math.Abs(_metric[i]) : 0;
        }
    }

    //// Compute the simple expected damage a bomb would score
    //private static void GetBlastPotential()
    //{
    //    _metric = new int[_field.Length];
    //    for (int i = 0; i < _field.Length; i++)
    //    {
    //        _metric[i] = (_field[i] > 0) ? 1 : 0;
    //        List<int> neighbours = GetNeighbours(i);
    //        if (neighbours != null)
    //        {
    //            foreach (int j in neighbours) _metric[i] += (_field[j] > 0) ? 1 : 0;
    //        }
    //    }            
    //}

    // Update the battle field upon dropping a bomb
    private static void Bomb(int cell)
    {
        List<int> neighbours = GetNeighbours(cell);
        foreach (int i in neighbours)
        {
            if (_field[i] > 0) _field[i]--;
        }
    }

    // Find the best bombing site - just return index of local maxima
    private static int FindBestBombingSite()
    {
        int max_idx = 0;
        int max_val = int.MinValue;
        for (int i = 0; i < _metric.Length; i++)
        {
            if (_metric[i] > max_val)
            {
                max_val = _metric[i];
                max_idx = i;
            }
        }
        return max_idx;
    }

    // Display the battle field on the console
    private static void PrintField(int cell)
    {
        for (int x = 0; x < _width; x++)
        {
            for (int y = 0; y < _length; y++)
            {
                int c = Get1DCoord(x, y);
                if (c == cell)
                    Console.Out.Write(string.Format("[{0}]", _field[c]).PadLeft(4));
                else
                    Console.Out.Write(string.Format(" {0} ", _field[c]).PadLeft(4));
            }
            Console.Out.Write(" || ");
            for (int y = 0; y < _length; y++)
            {
                int c = Get1DCoord(x, y);
                if (c == cell)
                    Console.Out.Write(string.Format("[{0}]", _metric[c]).PadLeft(4));
                else
                    Console.Out.Write(string.Format(" {0} ", _metric[c]).PadLeft(4));
            }
            Console.Out.WriteLine();
        }
        Console.Out.WriteLine();
    }           
  }
}

Le modèle de bombardement résultant est généré comme suit (valeurs de champ à gauche, métrique à droite)

Round 1:
  2   1   4   2   3   2   6  ||   7  16   8  10   4  18   6
  3   5   3   1   1   1   9  ||  11  18  18  21  17  28   5
  4  [2]  4   2   3   4   1  ||  19 [32] 21  20  17  24  22
  7   6   2   4   4   3   6  ||   8  17  20  14  16  22   8
  1   2   1   1   1   2   4  ||  14  15  14  11  13  16   7

Round 2:
  2   1   4   2   3   2   6  ||   5  13   6   9   4  18   6
  2   4   2   1   1  [1]  9  ||  10  15  17  19  17 [28]  5
  3   2   3   2   3   4   1  ||  16  24  18  17  17  24  22
  6   5   1   4   4   3   6  ||   7  14  19  12  16  22   8
  1   2   1   1   1   2   4  ||  12  12  12  10  13  16   7

Round 3:
  2   1   4   2   2   1   5  ||   5  13   6   7   3  15   5
  2   4   2   1   0   1   8  ||  10  15  17  16  14  20   2
  3  [2]  3   2   2   3   0  ||  16 [24] 18  15  16  21  21
  6   5   1   4   4   3   6  ||   7  14  19  11  14  19   6
  1   2   1   1   1   2   4  ||  12  12  12  10  13  16   7

Round 4:
  2   1   4   2   2   1   5  ||   3  10   4   6   3  15   5
  1   3   1   1   0   1   8  ||   9  12  16  14  14  20   2
  2   2   2   2   2  [3]  0  ||  13  16  15  12  16 [21] 21
  5   4   0   4   4   3   6  ||   6  11  18   9  14  19   6
  1   2   1   1   1   2   4  ||  10   9  10   9  13  16   7

Round 5:
  2   1   4   2   2   1   5  ||   3  10   4   6   2  13   3
  1   3   1   1   0  [0]  7  ||   9  12  16  13  12 [19]  2
  2   2   2   2   1   3   0  ||  13  16  15  10  14  15  17
  5   4   0   4   3   2   5  ||   6  11  18   7  13  17   6
  1   2   1   1   1   2   4  ||  10   9  10   8  11  13   5

Round 6:
  2   1   4   2   1   0   4  ||   3  10   4   5   2  11   2
  1   3   1   1   0   0   6  ||   9  12  16  11   8  13   0
  2   2   2   2   0   2   0  ||  13  16  15   9  14  14  15
  5   4  [0]  4   3   2   5  ||   6  11 [18]  6  11  15   5
  1   2   1   1   1   2   4  ||  10   9  10   8  11  13   5

Round 7:
  2   1   4   2   1   0   4  ||   3  10   4   5   2  11   2
  1   3   1   1   0   0   6  ||   8  10  13   9   7  13   0
  2  [1]  1   1   0   2   0  ||  11 [15] 12   8  12  14  15
  5   3   0   3   3   2   5  ||   3   8  10   3   8  15   5
  1   1   0   0   1   2   4  ||   8   8   7   7   9  13   5

Round 8:
  2   1   4   2   1   0   4  ||   1   7   2   4   2  11   2
  0   2   0   1   0   0   6  ||   7   7  12   7   7  13   0
  1   1   0   1   0   2   0  ||   8   8  10   6  12  14  15
  4   2   0   3   3  [2]  5  ||   2   6   8   2   8 [15]  5
  1   1   0   0   1   2   4  ||   6   6   6   7   9  13   5

Round 9:
  2   1   4   2   1   0   4  ||   1   7   2   4   2  11   2
  0   2   0   1   0   0   6  ||   7   7  12   7   6  12   0
  1   1   0   1   0  [1]  0  ||   8   8  10   5  10 [13] 13
  4   2   0   3   2   2   4  ||   2   6   8   0   6   9   3
  1   1   0   0   0   1   3  ||   6   6   6   5   8  10   4

Round 10:
  2   1   4   2   1   0   4  ||   1   7   2   4   2  10   1
  0   2  [0]  1   0   0   5  ||   7   7 [12]  7   6  11   0
  1   1   0   1   0   1   0  ||   8   8  10   4   8   9  10
  4   2   0   3   1   1   3  ||   2   6   8   0   6   8   3
  1   1   0   0   0   1   3  ||   6   6   6   4   6   7   2

Round 11:
  2   0   3   1   1   0   4  ||   0   6   0   3   0  10   1
  0   1   0   0   0  [0]  5  ||   4   5   5   5   3 [11]  0
  1   0   0   0   0   1   0  ||   6   8   6   4   6   9  10
  4   2   0   3   1   1   3  ||   1   5   6   0   5   8   3
  1   1   0   0   0   1   3  ||   6   6   6   4   6   7   2

Round 12:
  2   0   3   1   0   0   3  ||   0   6   0   2   1   7   1
  0   1   0   0   0   0   4  ||   4   5   5   4   1   7   0
  1   0   0   0   0  [0]  0  ||   6   8   6   4   5  [9]  8
  4   2   0   3   1   1   3  ||   1   5   6   0   4   7   2
  1   1   0   0   0   1   3  ||   6   6   6   4   6   7   2

Round 13:
  2   0   3   1   0   0   3  ||   0   6   0   2   1   6   0
  0   1   0   0   0   0   3  ||   4   5   5   4   1   6   0
  1  [0]  0   0   0   0   0  ||   6  [8]  6   3   3   5   5
  4   2   0   3   0   0   2  ||   1   5   6   0   4   6   2
  1   1   0   0   0   1   3  ||   6   6   6   3   4   4   0

Round 14:
  2   0   3   1   0  [0]  3  ||   0   5   0   2   1  [6]  0
  0   0   0   0   0   0   3  ||   2   5   4   4   1   6   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   5   5
  3   1   0   3   0   0   2  ||   0   4   5   0   4   6   2
  1   1   0   0   0   1   3  ||   4   4   5   3   4   4   0

Round 15:
  2   0   3   1   0   0   2  ||   0   5   0   2   1   4   0
  0   0   0   0   0   0   2  ||   2   5   4   4   1   4   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   4   4
  3   1   0   3   0  [0]  2  ||   0   4   5   0   4  [6]  2
  1   1   0   0   0   1   3  ||   4   4   5   3   4   4   0

Round 16:
  2  [0]  3   1   0   0   2  ||   0  [5]  0   2   1   4   0
  0   0   0   0   0   0   2  ||   2   5   4   4   1   4   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   3   3
  3   1   0   3   0   0   1  ||   0   4   5   0   3   3   1
  1   1   0   0   0   0   2  ||   4   4   5   3   3   3   0

Round 17:
  1   0   2   1   0   0   2  ||   0   3   0   1   1   4   0
  0   0   0   0   0   0   2  ||   1   3   3   3   1   4   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   3   3
  3   1  [0]  3   0   0   1  ||   0   4  [5]  0   3   3   1
  1   1   0   0   0   0   2  ||   4   4   5   3   3   3   0

Round 18:
  1   0   2   1   0   0   2  ||   0   3   0   1   1   4   0
  0   0   0   0   0   0   2  ||   1   3   3   3   1   4   0
  0   0   0   0   0   0   0  ||   3   3   2   2   2   3   3
  3  [0]  0   2   0   0   1  ||   0  [4]  2   0   2   3   1
  1   0   0   0   0   0   2  ||   2   4   2   2   2   3   0

Round 19:
  1   0   2   1   0  [0]  2  ||   0   3   0   1   1  [4]  0
  0   0   0   0   0   0   2  ||   1   3   3   3   1   4   0
  0   0   0   0   0   0   0  ||   2   2   2   2   2   3   3
  2   0   0   2   0   0   1  ||   0   2   2   0   2   3   1
  0   0   0   0   0   0   2  ||   2   2   2   2   2   3   0

Round 20:
  1  [0]  2   1   0   0   1  ||   0  [3]  0   1   1   2   0
  0   0   0   0   0   0   1  ||   1   3   3   3   1   2   0
  0   0   0   0   0   0   0  ||   2   2   2   2   2   2   2
  2   0   0   2   0   0   1  ||   0   2   2   0   2   3   1
  0   0   0   0   0   0   2  ||   2   2   2   2   2   3   0

Round 21:
  0   0   1   1   0   0   1  ||   0   1   0   0   1   2   0
  0   0   0   0   0   0   1  ||   0   1   2   2   1   2   0
  0   0   0   0   0   0   0  ||   2   2   2   2   2   2   2
  2   0   0   2   0  [0]  1  ||   0   2   2   0   2  [3]  1
  0   0   0   0   0   0   2  ||   2   2   2   2   2   3   0

Round 22:
  0   0   1   1   0   0   1  ||   0   1   0   0   1   2   0
  0   0   0   0   0   0   1  ||   0   1   2   2   1   2   0
 [0]  0   0   0   0   0   0  ||  [2]  2   2   2   2   1   1
  2   0   0   2   0   0   0  ||   0   2   2   0   2   1   1
  0   0   0   0   0   0   1  ||   2   2   2   2   2   1   0

Round 23:
  0   0   1   1   0   0   1  ||   0   1   0   0   1   2   0
  0   0  [0]  0   0   0   1  ||   0   1  [2]  2   1   2   0
  0   0   0   0   0   0   0  ||   1   1   2   2   2   1   1
  1   0   0   2   0   0   0  ||   0   1   2   0   2   1   1
  0   0   0   0   0   0   1  ||   1   1   2   2   2   1   0

Round 24:
  0   0   0   0   0   0   1  ||   0   0   0   0   0   2   0
  0   0   0   0   0   0   1  ||   0   0   0   0   0   2   0
  0   0  [0]  0   0   0   0  ||   1   1  [2]  2   2   1   1
  1   0   0   2   0   0   0  ||   0   1   2   0   2   1   1
  0   0   0   0   0   0   1  ||   1   1   2   2   2   1   0

Round 25:
  0   0   0   0   0  [0]  1  ||   0   0   0   0   0  [2]  0
  0   0   0   0   0   0   1  ||   0   0   0   0   0   2   0
  0   0   0   0   0   0   0  ||   1   1   1   1   1   1   1
  1   0   0   1   0   0   0  ||   0   1   1   0   1   1   1
  0   0   0   0   0   0   1  ||   1   1   1   1   1   1   0

Round 26:
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
 [0]  0   0   0   0   0   0  ||  [1]  1   1   1   1   0   0
  1   0   0   1   0   0   0  ||   0   1   1   0   1   1   1
  0   0   0   0   0   0   1  ||   1   1   1   1   1   1   0

Round 27:
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0  [0]  0   0   0   0  ||   0   0  [1]  1   1   0   0
  0   0   0   1   0   0   0  ||   0   0   1   0   1   1   1
  0   0   0   0   0   0   1  ||   0   0   1   1   1   1   0

Round 28:
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0  [0]  0  ||   0   0   0   0   0  [1]  1
  0   0   0   0   0   0   1  ||   0   0   0   0   0   1   0

Done in 28 rounds
1
zeFrenchy

Vous pouvez utiliser la planification de l'espace d'état. Par exemple, utiliser A * (ou l’une de ses variantes) associé à une méthode heuristique f = g + h comme ça:

  • g: nombre de bombes larguées jusqu'à présent
  • h: somme sur toutes les valeurs de la grille divisée par 9 (ce qui est le meilleur résultat, ce qui signifie que nous avons une heuristique admissible)
1
キキジキ

Ceci peut être résolu en utilisant un arbre de profondeur O (3 ^ (n)). Où n est la somme de tous les carrés.

D'abord, considérez qu'il est trivial de résoudre le problème avec un arbre de O (9 ^ n), considérons simplement tous les emplacements de bombardement possibles. Pour un exemple, voir implémentation d'Alfe .

Nous réalisons ensuite que nous pouvons travailler pour bombarder de bas en haut tout en obtenant un schéma de bombardement minimal.

  1. Commencez par le coin inférieur gauche.
  2. Bombardez-le jusqu'à l’oubli avec les seuls jeux qui ont un sens (en haut et à droite).
  3. Déplacez une case à droite.
  4. Alors que la cible a une valeur supérieure à zéro, considérez chacune des 2 parties qui ont du sens (tout en haut ou à droite), réduisez la valeur de la cible de un et créez une nouvelle branche pour chaque possibilité.
  5. Déplacez un autre à droite.
  6. Alors que la cible a une valeur supérieure à zéro, considérez chacune des 3 phases qui ont un sens (haut gauche, haut et haut droite), réduisez la valeur de la cible de un et créez une nouvelle branche pour chaque possibilité.
  7. Répétez les étapes 5 et 6 jusqu'à ce que la ligne soit éliminée.
  8. Déplacez-vous dans une rangée et répétez les étapes 1 à 7 jusqu'à ce que le puzzle soit résolu.

Cet algorithme est correct car

  1. Il est nécessaire de compléter chaque ligne à un moment donné.
  2. Pour terminer une ligne, il faut toujours jouer soit au-dessus, soit au-dessous, ou à l'intérieur de cette ligne.
  3. Il est toujours aussi bon ou meilleur de choisir un jeu au-dessus de la rangée non dégagée la plus basse qu'un jeu au rang ou au-dessous du rang.

En pratique, cet algorithme fera régulièrement mieux que son maximum théorique car il bombardera régulièrement les voisins et réduira la taille de la recherche. Si nous supposons que chaque bombardement diminue la valeur de 4 cibles supplémentaires, notre algorithme s'exécutera en O (3 ^ (n/4)) ou approximativement O (1.3 ^ n).

Comme cet algorithme est toujours exponentiel, il serait sage de limiter la profondeur de la recherche. Nous pourrions limiter le nombre de branches autorisées à un certain nombre, X, et une fois que nous sommes aussi profonds, nous forçons l'algorithme à choisir le meilleur chemin identifié jusqu'à présent (celui qui a la somme totale minimale de la carte dans l'une de ses feuilles terminales. ). Ensuite, notre algorithme est garanti pour fonctionner en temps O (3 ^ X), mais il n’est pas garanti pour obtenir la réponse correcte. Cependant, nous pouvons toujours augmenter X et tester empiriquement si le compromis entre un calcul accru et de meilleures réponses en vaut la peine.

1
Ben Haley

Plusieurs réponses donnent jusqu'ici un temps exponentiel, certaines impliquent une programmation dynamique. Je doute que cela soit nécessaire.

Ma solution est O (mnS) m, n sont des dimensions du tableau, [~ # ~] s [~ # ~] est la somme de tous les entiers. L'idée est plutôt brutale: trouver l'emplacement qui peut tuer le plus à chaque fois et se terminer à 0.

Il donne 28 coups pour le tableau donné, et affiche également le tableau après chaque chute.

Code complet et explicite:

import Java.util.Arrays;

public class BombMinDrops {

    private static final int[][] BOARD = {{2,3,4,7,1}, {1,5,2,6,2}, {4,3,4,2,1}, {2,1,2,4,1}, {3,1,3,4,1}, {2,1,4,3,2}, {6,9,1,6,4}};
    private static final int ROWS = BOARD.length;
    private static final int COLS = BOARD[0].length;
    private static int remaining = 0;
    private static int dropCount = 0;
    static {
        for (int i = 0; i < ROWS; i++) {
            for (int j = 0; j < COLS; j++) {
                remaining = remaining + BOARD[i][j];
            }
        }
    }

    private static class Point {
        int x, y;
        int kills;

        Point(int x, int y, int kills) {
            this.x = x;
            this.y = y;
            this.kills = kills;
        }

        @Override
        public String toString() {
            return dropCount + "th drop at [" + x + ", " + y + "] , killed " + kills;
        }
    }

    private static int countPossibleKills(int x, int y) {
        int count = 0;
        for (int row = x - 1; row <= x + 1; row++) {
            for (int col = y - 1; col <= y + 1; col++) {
                try {
                    if (BOARD[row][col] > 0) count++;
                } catch (ArrayIndexOutOfBoundsException ex) {/*ignore*/}
            }
        }

        return count;
    }

    private static void drop(Point here) {
        for (int row = here.x - 1; row <= here.x + 1; row++) {
            for (int col = here.y - 1; col <= here.y + 1; col++) {
                try {
                    if (BOARD[row][col] > 0) BOARD[row][col]--;
                } catch (ArrayIndexOutOfBoundsException ex) {/*ignore*/}
            }
        }

        dropCount++;
        remaining = remaining - here.kills;
        print(here);
    }

    public static void solve() {
        while (remaining > 0) {
            Point dropWithMaxKills = new Point(-1, -1, -1);
            for (int i = 0; i < ROWS; i++) {
                for (int j = 0; j < COLS; j++) {
                    int possibleKills = countPossibleKills(i, j);
                    if (possibleKills > dropWithMaxKills.kills) {
                        dropWithMaxKills = new Point(i, j, possibleKills);
                    }
                }
            }

            drop(dropWithMaxKills);
        }

        System.out.println("Total dropped: " + dropCount);
    }

    private static void print(Point drop) {
        System.out.println(drop.toString());
        for (int[] row : BOARD) {
            System.out.println(Arrays.toString(row));
        }

        System.out.println();
    }

    public static void main(String[] args) {
        solve();
    }

}
0
PoweredByRice

L'algorithme le plus lent mais le plus simple et sans erreur est de générer et de tester toutes les possibilités valables. car ce cas est très simple (car le résultat est indépendant de l'ordre de placement de la bombe).

  1. créer une fonction qui s'applique N fois bomp
  2. créer une boucle pour toutes les posibilités de bompb-placement/bomb-count (arrêt lorsque matrix == 0)
  3. rappelez-vous toujours la meilleure solution.
  4. à la fin de la boucle, vous avez la meilleure solution
    • non seulement le nombre de bombes, mais aussi leur placement

le code peut ressembler à ceci:

void copy(int **A,int **B,int m,int n)
    {
    for (int i=0;i<m;i++)
     for (int j=0;i<n;j++)
       A[i][j]=B[i][j];
    }

bool is_zero(int **M,int m,int n)
    {
    for (int i=0;i<m;i++)
     for (int j=0;i<n;j++)
      if (M[i][j]) return 0;
    return 1;
    }

void drop_bomb(int **M,int m,int n,int i,int j,int N)
    {
    int ii,jj;
    ii=i-1; jj=j-1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i-1; jj=j  ; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i-1; jj=j+1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i  ; jj=j-1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i  ; jj=j  ; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i  ; jj=j+1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i+1; jj=j-1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i+1; jj=j  ; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i+1; jj=j+1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    }

void solve_problem(int **M,int m,int n)
    {
    int i,j,k,max=0;
    // you probably will need to allocate matrices P,TP,TM yourself instead of this:
    int P[m][n],min;             // solution: placement,min bomb count
    int TM[m][n],TP[m][n],cnt;   // temp
    for (i=0;i<m;i++)            // max count of bomb necessary to test
     for (j=0;j<n;j++)
      if (max<M[i][j]) max=M[i][j];
    for (i=0;i<m;i++)            // reset solution
     for (j=0;j<n;j++)
      P[i][j]=max;
    min=m*n*max; 
        copy(TP,P,m,n); cnt=min;

    for (;;)  // generate all possibilities
        {
        copy(TM,M,m,n);
        for (i=0;i<m;i++)   // test solution
         for (j=0;j<n;j++)
          drop_bomb(TM,m,n,TP[i][j]);
        if (is_zero(TM,m,n))// is solution
         if (min>cnt)       // is better solution -> store it
            {
            copy(P,TP,m,n); 
            min=cnt;    
            }
        // go to next possibility
        for (i=0,j=0;;)
            {
            TP[i][j]--;
            if (TP[i][j]>=0) break;
            TP[i][j]=max;
                 i++; if (i<m) break;
            i=0; j++; if (j<n) break;
            break;
            }
        if (is_zero(TP,m,n)) break;
        }
    //result is in P,min
    }

cela peut être optimisé de nombreuses manières, ... le plus simple est de réinitialiser la solution avec M matrix, mais vous devez modifier la valeur maximale ainsi que le code de décrémentation TP [] []

0
Spektre

C'était une réponse à la première question posée. Je n'avais pas remarqué qu'il avait changé les paramètres.

Créez une liste de toutes les cibles. Attribuez une valeur à la cible en fonction du nombre de valeurs positives affectées par une chute (elle-même et tous les voisins). La valeur la plus élevée serait un neuf.

Triez les cibles en fonction du nombre de cibles impactées (Décroissant), avec un tri secondaire décroissant sur la somme de chaque cible impactée.

Déposez une bombe sur la cible la mieux classée, recalculez les cibles et répétez l'opération jusqu'à ce que toutes les valeurs cibles soient égales à zéro.

D'accord, ce n'est pas toujours le plus optimal. Par exemple,

100011
011100
011100
011100
000000
100011

Cette approche nécessiterait 5 bombes à nettoyer. De manière optimale, cependant, vous pourriez le faire en 4. Pourtant, c'est sacrément proche et il n'y a pas de retour en arrière. Dans la plupart des situations, ce sera optimal ou très proche.

En utilisant les numéros de problèmes originaux, cette approche résout 28 bombes.

Ajout de code pour illustrer cette approche (en utilisant un formulaire avec un bouton):

         private void button1_Click(object sender, EventArgs e)
    {
        int[,] matrix = new int[10, 10] {{5, 20, 7, 1, 9, 8, 19, 16, 11, 3}, 
                                         {17, 8, 15, 17, 12, 4, 5, 16, 8, 18},
                                         { 4, 19, 12, 11, 9, 7, 4, 15, 14, 6},
                                         { 17, 20, 4, 9, 19, 8, 17, 2, 10, 8},
                                         { 3, 9, 10, 13, 8, 9, 12, 12, 6, 18}, 
                                         {16, 16, 2, 10, 7, 12, 17, 11, 4, 15},
                                         { 11, 1, 15, 1, 5, 11, 3, 12, 8, 3},
                                         { 7, 11, 16, 19, 17, 11, 20, 2, 5, 19},
                                         { 5, 18, 2, 17, 7, 14, 19, 11, 1, 6},
                                         { 13, 20, 8, 4, 15, 10, 19, 5, 11, 12}};


        int value = 0;
        List<Target> Targets = GetTargets(matrix);
        while (Targets.Count > 0)
        {
            BombTarget(ref matrix, Targets[0]);
            value += 1;
            Targets = GetTargets(matrix);
        }
        Console.WriteLine( value);
        MessageBox.Show("done: " + value);
    }

    private static void BombTarget(ref int[,] matrix, Target t)
    {
        for (int a = t.x - 1; a <= t.x + 1; a++)
        {
            for (int b = t.y - 1; b <= t.y + 1; b++)
            {
                if (a >= 0 && a <= matrix.GetUpperBound(0))
                {
                    if (b >= 0 && b <= matrix.GetUpperBound(1))
                    {
                        if (matrix[a, b] > 0)
                        {
                            matrix[a, b] -= 1;
                        }
                    }
                }
            }
        }
        Console.WriteLine("Dropped bomb on " + t.x + "," + t.y);
    }

    private static List<Target> GetTargets(int[,] matrix)
    {
        List<Target> Targets = new List<Target>();
        int width = matrix.GetUpperBound(0);
        int height = matrix.GetUpperBound(1);
        for (int x = 0; x <= width; x++)
        {
            for (int y = 0; y <= height; y++)
            {
                Target t = new Target();
                t.x = x;
                t.y = y;
                SetTargetValue(matrix, ref t);
                if (t.value > 0) Targets.Add(t);
            }
        }
        Targets = Targets.OrderByDescending(x => x.value).ThenByDescending( x => x.sum).ToList();
        return Targets;
    }

    private static void SetTargetValue(int[,] matrix, ref Target t)
    {
        for (int a = t.x - 1; a <= t.x + 1; a++)
        {
            for (int b = t.y - 1; b <= t.y + 1; b++)
            {
                if (a >= 0 && a <= matrix.GetUpperBound(0))
                {
                    if (b >= 0 && b <= matrix.GetUpperBound(1))
                    {
                        if (matrix[ a, b] > 0)
                        {
                            t.value += 1;
                            t.sum += matrix[a,b];
                        }

                    }
                }
            }
        }

    }

Un cours dont vous aurez besoin:

        class Target
    {
        public int value;
        public int sum;
        public int x;
        public int y;
    }
0
Anthony Queen

Tout ce problème revient à calculer une distance de montage. Calculez simplement une variante de la distance de Levenshtein entre la matrice donnée et la matrice zéro, les modifications étant remplacées par des bombardements, en utilisant la programmation dynamique pour stocker les distances entre les tableaux intermédiaires. Je suggère d'utiliser un hachage des matrices comme clé. En pseudo-Python:

memo = {}

def bomb(matrix,i,j):
    # bomb matrix at i,j

def bombsRequired(matrix,i,j):
    # bombs required to zero matrix[i,j]

def distance(m1, i, len1, m2, j, len2):
    key = hash(m1)
    if memo[key] != None: 
        return memo[key]

    if len1 == 0: return len2
    if len2 == 0: return len1

    cost = 0
    if m1 != m2: cost = m1[i,j]
    m = bomb(m1,i,j)
    dist = distance(str1,i+1,len1-1,str2,j+1,len2-1)+cost)
    memo[key] = dist
    return dist
0
Aerimore