これは、Google Code Jamの認定ラウンド(現在は終了しています)の問題です。この問題を解決する方法は?
注:回答で説明されている方法とは異なる方法がある場合は、それを共有してください。この問題を解決するためのさまざまな方法に関する知識を広げることができます。
マインスイーパは1980年代に普及したコンピューターゲームであり、MicrosoftWindowsオペレーティングシステムの一部のバージョンにはまだ含まれています。この問題にも同様の考えがありますが、マインスイーパをプレイしたことを前提とはしていません。
この問題では、同じセルのグリッドでゲームをプレイしています。各セルの内容は最初は非表示になっています。グリッドのM個の異なるセルにM個の地雷が隠されています。他のセルには地雷が含まれていません。任意のセルをクリックして表示できます。明らかにされたセルに地雷が含まれている場合、ゲームは終了し、あなたは負けます。それ以外の場合、表示されたセルには0から8までの数字が含まれます。これは、地雷を含む隣接セルの数に対応します。 2つのセルがコーナーまたはエッジを共有している場合、それらは隣接セルです。さらに、公開されたセルに0が含まれている場合、公開されたセルのすべての隣接セルも自動的に再帰的に公開されます。地雷を含まないすべてのセルが明らかになると、ゲームは終了し、あなたが勝ちます。
たとえば、ボードの初期構成は次のようになります(「*」は地雷を示し、「c」は最初にクリックされたセルです)。
*..*...**.
....*.....
..c..*....
........*.
..........
クリックしたセルに隣接する地雷がないため、表示されると0になり、隣接する8個のセルも表示されます。このプロセスが続行され、次のボードが作成されます。
*..*...**.
1112*.....
00012*....
00001111*.
00000001..
この時点では、地雷(「。」の文字で示されている)を含まない未公開のセルがまだあるため、ゲームを続行するには、プレーヤーはもう一度クリックする必要があります。
あなたはできるだけ早くゲームに勝ちたいと思っています。ワンクリックで勝つことほど速いものはありません。ボードのサイズ(R x C)と隠された地雷の数Mを考えると、ワンクリックで勝つことは可能ですか(可能性は低いですが)?クリックする場所を選択できます。可能であれば、[出力]セクションの仕様に従って、有効な地雷構成とクリックの座標を印刷します。それ以外の場合は、「不可能」と印刷してください。
私の試した解決策:
したがって、ソリューションでは、各非マイニングノードが他の非マイニングノードと3x3マトリックスにあること、またはノードがグリッドのエッジにある場合は3x2または2x2マトリックスにあることを確認する必要があります。これを0Matrixと呼びましょう。したがって、0Matrix内のすべてのノードには、すべて非マイニングネイバーがあります。
まず、必要な地雷が少ないか、空のノードが少ないかを確認します
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
たとえば、8つのクリーンノードを必要とする4x4グリッドがある場合、次の手順に従います。
// Initial grid of all mines
* * * *
* * * *
* * * *
* * * *
// Populating 3's horizontally
. * * *
. * * *
. * * *
* * * *
. . * *
. . * *
. . * *
* * * *
// Populating 2's continuing in the same direction as 3's
. . . *
. . . *
. . * *
* * * *
別の例:11個のクリアノードが必要な4x4グリッド。出力:
. . . .
. . . .
. . . *
* * * *
別の例:14個のクリアノードが必要な4x4グリッド。出力:
// Insert the 4 3's horizontally, then switch to vertical to insert the 2's
. . . .
. . . .
. . . .
. . * *
これで、完全に入力されたグリッドができました。(0、0)をクリックすると、ワンクリックで解決できます。
私のソリューションはほとんどの場合に機能しますが、送信に合格しませんでした(225ケースの出力ファイル全体をチェックしました)ので、いくつかの問題があると思います。より良いソリューションがあると確信しています。
まず、非地雷セルの数であるN
を定義しましょう。
_N = R * C - M
_
簡単な解決策は、N
非マイニングセルの領域を上から下に1行ずつ埋めることです。 _R=5
_、_C=5
_、_M=12
_の例:
_c....
.....
...**
*****
*****
_
あれは:
N / C
_行を上から下に非地雷で埋めます。N % C
_非地雷で埋めます。あなたが気にしなければならない特別なケースはほんのわずかです。
_N=1
_の場合、どの構成も正しい解決策です。
_R=1
_の場合は、N
非地雷を左から右に入力するだけです。 _C=1
_の場合、N
行を(単一の)非マイニングで埋めます。
N
が偶数の場合、4以上である必要があります。
N
が奇数の場合、> = 9である必要があります。また、R
およびC
は> = 3である必要があります。
そうでなければ解決策はありません。
N
が偶数で、少なくとも2行を非地雷で埋めることができない場合は、最初の2行を_N / 2
_非地雷で埋めます。
N
が奇数で、少なくとも2行を非地雷で埋めることができず、3行目を3つの非地雷で埋めることができない場合は、最初の2行を_(N - 3) / 2
_非地雷で埋めます。 3つの非鉱山の3行目。
_N % C = 1
_の場合、最後の非地雷を最後の完全な行から次の行に移動します。
_R=5
_、_C=5
_、_M=9
_の例:
_c....
.....
....*
..***
*****
_
これらのルールを実装し、結果の地雷フィールドの説明をO(1)
に返すアルゴリズムを作成することができます。もちろん、グリッドの描画にはO(R*C)
が必要です。また、Code Jam Judgeによって受け入れられたこれらのアイデアに基づいて、Perlで実装を作成しました。
この問題には、小規模なテストケースと大規模なテストケースの両方に合格する、より一般的な解決策があります。それはすべての特別な場合を考える必要をなくし、ボードの寸法が何であるかを気にせず、バックトラッキングを必要としません。
[〜#〜]アルゴリズム[〜#〜]
基本的な考え方は、地雷でいっぱいのグリッドから始めて、ボード上に正しい数の地雷ができるまで、セル{0、0}からそれらを削除することです。
明らかに、次に削除する地雷と、正しい数の地雷を削除できない場合を決定する方法が必要です。これを行うために、ボードを表すint[][]
を保持できます。地雷のある各セルには-1
が含まれ、地雷のないセルには、セルに隣接する地雷の数である整数が含まれます。実際のゲームと同じです。
また、「フロンティア」の概念を定義します。これは、ゼロ以外のすべての非地雷セル、つまり地雷が隣接するセルです。
例:構成:
c . *
. . *
. . *
* * *
次のように表されます:
0 2 -1
0 3 -1
2 5 -1
-1 -1 -1
また、フロンティアには次の値のセルが含まれます:2, 3, 5, 2
地雷を取り除くときの戦略は次のとおりです。
Javaでは次のようになります:
// 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;
}
PROOF/INTUITION
まず、ボード上にこのクリックが発生する可能性のあるセルが1つしかない場合でも、シングルクリックで解決できる場合、ボードはvalidとして定義されます。したがって、ボードが有効であるためには、すべての非マイニングセルが値0のセルに隣接している必要があります。そうでない場合、セルは到達不能として定義されます。これは、0セルに隣接するすべてのセルが非地雷であることが確実にわかっているため、安全に公開でき、ゲームはプレーヤーに対して自動的にそれを実行します。
このアルゴリズムを証明するための重要なポイントは、ボードを有効な状態に保つために、最小のフロンティアセルを囲むすべての地雷を削除する必要があるということです。これは、ボード(上記のような)を引き出し、最も低い値のセル(この場合は右上の2)を選択するだけで非常に簡単に証明できます。鉱山が1つだけ削除された場合、ボードは無効になり、次の2つの状態のいずれかになります。
0 1 1
0 2 -1
2 5 -1
-1 -1 -1
または
0 1 -1
0 2 2
2 5 -1
-1 -1 -1
どちらにも到達不能セルがあります。
したがって、常に最小のフロンティアセルを選択するとボードが有効な状態に保たれることは事実であり、私の最初の本能は、これらのセルを選択し続けるとすべての有効な状態が通過するということでしたが、これは正しくありません。これは、4 4 7
などのテストケースで説明できます(したがって、9つの非地雷セルがあります)。次に、次のボードを検討してください。
0 2 -1 -1
2 5 -1 -1
-1 -1 -1 -1
-1 -1 -1 -1
最小のフロンティアセルを選択し続けると、アルゴリズムが次のようになる可能性があります。
0 2 -1 -1
0 3 -1 -1
0 3 -1 -1
0 2 -1 -1
つまり、この場合、ボードを完成させるために1つの鉱山だけを削除することは現在不可能です。ただし、残りの地雷の数(存在する場合)に一致するフロンティアセルを選択すると、5つが選択され、3x3の正方形の非地雷とテストケースの正しい解が得られます。
この時点で、次の範囲のすべてのテストケースでアルゴリズムを試してみることにしました。
0 < R < 20
0 < C < 20
0 ≤ M < R * C
そして、不可能な構成をすべて正しく識別し、可能な構成に対して賢明なソリューションのように見えるものを構築することに成功したことがわかりました。
残りの地雷の数(存在する場合)と同じ値を持つフロンティアセルを選択することが正しい理由の背後にあるさらなる直感は、アルゴリズムが奇数を必要とするソリューションの構成を見つけることができるということです。 )非鉱山の数。
このアルゴリズムを最初に実装したとき、私は非鉱山エリアを正方形の配置で構築するヒューリスティックを書くつもりでした。 4 4 7
テストケースをもう一度考えると、次のようになります。
0 0 2 -1
0 1 4 -1
2 4 -1 -1
-1 -1 -1 -1
フロンティアに1
があり、削除された最後のセルが正方形を完成させて次のようになっていることに注目してください。
c . . *
. . . *
. . . *
* * * *
これは、ヒューリスティックがわずかに次のように変化することを意味します。
これは、フロンティアセルのFIFOキューを保持することで実装できますが、最初に思ったよりも難しいことにすぐに気付きました。これは、フロンティアセルの値が相互に依存しているためです。したがって、フロンティアセルのコレクションを正しい状態に保ち、セル値をあらゆる種類のハッシュ値などで使用しないように注意する必要があります。これは可能であると確信していますが、残りの除去と等しい値を持つセルを選択するための追加のヒューリスティックが機能しました。これは、より簡単なアプローチが好きだったようです。
これは私の コード です。 _number of rows=1
_または_number of columns=1
_の場合、またはnumber of mines=(r*c)-1
の場合など、さまざまなケースを取り上げて解決しました。
クリックするレイアウト上の位置は、毎回_a[r-1][c-1]
_( '0'インデックス付き)に配置されます。
この質問に対して、私はいくつかの間違った試みをしました、そして、私は新しいケースを見つけ続けるたびに。 goto
を使用して解決できないいくつかのケースを排除し、印刷が不可能なところでジャンプして終了させました。非常に単純な解決策(個別に可能なさまざまなケースをコーディングしたので、実際にはブルートフォースソリューションと言えます)。これは私のコードの 社説 です。そして github 。
私はこれを2つの最初の特殊なケースに分け、次に一般的なアルゴリズムを使用しました。 tl; drバージョンは、左上から空白の正方形を作成するためのものです。他の回答と同様ですが、特殊なケースが少なくなっています。
空白スペースは1つだけです。左上隅をクリックして終了します。
Rx1でも1xCでもないグリッドを持つ2つまたは3つの空白スペース。これは不可能なので、早く失敗します。
常に左上隅をクリックしてください。左上の2x2の空白の正方形から始めます(少なくとも4つの空白があります)。次に、残りの空白を追加する必要があります。次に、空白がなくなるまで、一方のエッジに沿って、次にもう一方のエッジに沿って正方形を展開します。
ブランキング注文の例:
C 2 6 12
1 3 7 13
4 5 8 14
9 10 11 15
新しいエッジを開始するとき、これを有効にするには、少なくとも2つの空白スペースを配置する必要があることに注意してください。したがって、配置する空白が1つしかない場合、これは無効である必要があります(Edgeの長さが1でない場合)。私のロジックは次のようになりました。
if maxEdgeLength > 1 and remainingBlanks == 1:
print('Impossible')
return
ただし、最後のEdgeの終わりを省略できた可能性があります。これにより、2つの空白ができます。もちろん、最後のエッジの長さが2ブランクを超えている場合にのみ、最後のブランクを省略することができます。
この特別な場合の私のロジックは次のようになりました。
if remainingBlanks == 1 and lastEdgeSize > 2:
mineMatrix[lastBlank] = '*'
blanks += 1
バックトラックで検索を使用しましたが、小さな入力しか解決できませんでした。
基本的に、アルゴリズムは地雷でいっぱいのボードから始まり、最初の「クリック」でボードを解決する方法で地雷を削除しようとします。キャッチは、「クリック」を別のセルに展開できるようにするために、他のすべての隣接セルもクリアする必要がある別のセルから展開が行われることです。場合によっては、別のセルに拡張するには、他の地雷を削除して、必要以上に地雷が少なくなる必要があります。アルゴリズムは、そのような位置に到達するとバックトラックします。
たぶん、反対のことをする方が簡単です。空のボードから始めて、最初のクリックの「拡大」を妨げない方法で各鉱山を追加します。
完全なPythonコードは以下のとおりです:
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)
私の戦略はあなたの戦略と非常に似ていて、大小両方を通過しました。以下のケースについて考えましたか?
R * C-M = 1
行は1つだけです
行は2つだけです
R> CのときにRとCを反転しました。
グリッドをすべての地雷で埋め、クリックをどこかに置きます。
左/右(または上/下)を順番に入力します:クリック、非地雷、地雷(例:_c...****
_)。
不可能な
「空の」グリッド(すべて_.
_ s)から始めて、クリックを隅に配置しました(クリックには左上隅を使用し、右下から地雷を埋め始めます)。
「現在の」行と列として_R1
_と_C1
_を使用します。
行または列を埋めるのに十分な地雷がありますが、削除しても行または列が1つも残っていません(while((M >= R1 && C1 > 2) || (M >= C1 && R1 > 2))
)が、グリッドを「トリミング」します(地雷を埋めて_R1
_または_C1
_)最短の辺を使用して、その数の地雷を削除します。したがって、残り6個の地雷がある4x5は、残り2個の地雷がある4x4になります。
M == min(R1,C1)-1
かどうかを確認します。その場合は、最短のエッジから1行または1列に単一の地雷を配置し、最短のエッジを残りの地雷で埋める必要があります。視覚化を支援するために、グリッドに地雷を入力する順序を数字で示します
_R = 7, C = 6, M = 29
_
_c...42
....42
...742
..6742
555542
333332
111111
_
アルゴリズムを正しくするためにいくつかの異なる試みが必要でしたが、私はPHPで私のものを書き、小さいものと大きいものの両方を正しくしました。
この問題に対する私のアプローチは次のとおりです。
要約すると、c
は、何があっても常にその隣に非地雷セルを持ちます。そうでなければ、それは不可能です。この解決策は私には理にかなっており、出力をサンプルと小さな入力に対してチェックしましたが、受け入れられませんでした。
これが私のコードです:
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()
ウェブサイトのサンプルや提供された小さな入力に対してはかなりうまく機能しましたが、何かが足りないようです。
サンプルへの出力は次のとおりです。
Case #1:
Impossible
Case #2:
*
.
c
Case #3:
Impossible
Case #4:
***....
.......
.......
......c
Case #5:
**********
**********
**********
**********
**********
**********
**********
**********
**........
.........c
更新: vinaykumarの社説を読んで、私のソリューションの何が問題になっているのか理解しています。私がカバーすべきだったマインスイーパの基本的なルールは、ほとんどです。
コメント付きのコードzの自明。 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};
}
}
私もこの質問で運試しをしましたが、何らかの理由でチェックに合格しませんでした。
「c」に4つのセルが必要で、その境界が「。」であるため、(rows * cols-4)未満の地雷があれば、(3x3行列)で解決できると考えました。
私のアルゴリズムは次のとおりです。
可解?:
rows*cols - 4 == maximum mines
)ビルドソリューション
rows*cols matrix
、デフォルト値はnilm[0][0]
に移動し、'c'
を割り当てますm[0][0]
の周囲を'.'
で定義します'*'
を割り当ててから、'.'
を割り当てます解決策は見つけることができます ここ 。以下のページの内容。
有効な地雷構成を生成する方法はたくさんあります。この分析では、考えられるすべてのケースを列挙し、各ケース(存在する場合)に対して有効な構成を生成しようとします。後で、ある程度の洞察を得た後、有効な地雷構成(存在する場合)を生成するためのアルゴリズムを実装するのが簡単になります。
考えられるすべてのケースを列挙する
些細なケースをチェックすることから始めます。
空のセルが1つしかない場合は、クリックしたセルを除くすべてのセルを地雷で埋めることができます。 R = 1またはC = 1の場合、地雷はそれぞれ左から右または上から下に配置し、それぞれ右端または一番下のセルをクリックできます。ボードが上記の2つの些細なケースにない場合は、ボードのサイズが少なくとも2 x2であることを意味します。次に、手動で次のことを確認できます。
空のセルの数が2または3の場合、有効な構成を持つことはできません。 R = 2またはC = 2の場合、有効な構成はMが偶数の場合にのみ存在します。たとえば、R = 2、C = 7、M = 5の場合、Mは奇数であるため、不可能です。ただし、M = 6の場合、次のように、ボードの左側に地雷を配置して右下をクリックできます:* .... *... cボードが上記のいずれにも該当しない場合は、ボードが少なくとも3 x3サイズであることを意味します。この場合、空のセルの数が9より大きい場合は、常に有効な地雷構成を見つけることができます。これを行う1つの方法は次のとおりです。
空のセルの数が3 * C以上の場合、地雷は上から下に向かって1行ずつ配置できます。残りの地雷の数が列を完全に埋めることができるか、C-2未満の場合は、地雷を左から右にその列に配置します。それ以外の場合、残りの地雷の数は正確にC-1であり、最後の地雷を次の行に配置します。例えば: ****** ****** *****。 **** .. ......-> * ..... ...... ...... ..... c ..... c空のセルの数の場合は3 * C未満ですが、少なくとも9であり、最初に最後の3行を除くすべての行を地雷で埋めます。最後の3行については、残りの地雷を左端の列から列ごとに入力します。最後の列の残りの地雷が2つである場合、最後の地雷は次の列に配置する必要があります。例:****** ******....-> *... ** .... * ..... * .... c * .... cこれで、右下隅の3 x3の正方形のセルにある最大9つの空のセルが残ります。この場合、空のセルの数が5または7の場合、有効な地雷構成を持つことが不可能であることを手動で確認できます。それ以外の場合は、その3 x3の正方形セル内の空のセルの数ごとに有効な構成をハードコーディングできます。
ため息...それはカバーする多くのケースでした!ソリューションをコーディングするときに、コーナーケースを見逃さないことをどのように確信できますか?
ブルートフォースアプローチ
入力が小さい場合、ボードサイズは最大5 x 5です。可能なすべての(25選択M)鉱山構成をチェックして、有効な構成を見つけることができます(つまり、構成内の空のセルをクリックすると、他のすべての空のセルが表示されます)。地雷の構成が有効かどうかを確認するには、クリックした空のセルからフラッドフィルアルゴリズム(または単純な幅優先探索)を実行し、他のすべての空のセルに到達できることを確認します(つまり、接続された1つのコンポーネントにあります)。 。考えられるすべてのクリック位置も確認する必要があることに注意してください。このブルートフォースアプローチは、小さな入力に対して十分に高速です。
強引なアプローチを使用して、上記の列挙戦略に偽陰性があるかどうかを確認できます(R、C、Mの値が小さい場合)。有効な地雷構成が存在する場合、偽陰性が見つかりますが、上記の列挙戦略では不可能が生じます。列挙戦略でフォールスネガティブが生成されないことが確認できたら、それを使用して大きな入力を解決できます。
実装が簡単なアプローチ
上記の列挙戦略を使用していくつかの有効な地雷構成を試してみると、パターンに気付く場合があります。有効な地雷構成では、特定の行の地雷の数は常にその下の行の地雷の数以上であり、すべての地雷は一列に左揃えになっています。この洞察により、次の行に入力し、現在の行の構成が無効な場合はプルーニングするときに、地雷の数を増やしずに上から下に行ごとに地雷を配置する、より単純なバックトラッキングアルゴリズムを実装できます(右下のセルをクリックして確認できます)。剪定を伴うこのバックトラックは、妥当な時間で最大50 x 50サイズのボードを処理でき、実装が簡単です(つまり、コーナー/トリッキーなケースを列挙する必要はありません)。
コンテストの時間が短かった場合、考えられるすべてのケースを列挙するのに十分な時間がない可能性があります。この場合、バックトラッキングアルゴリズム(または実装が簡単な他のアルゴリズム)に賭けることをお勧めします。そのようなアルゴリズムを見つけることは芸術です:)。