30

これは、Google Code Jam 予選ラウンドの問題です (現在は終了しています)。この問題を解決するには?

注: 回答で説明した方法とは異なる方法がある場合は、それを共有してください。この問題を解決するさまざまな方法についての知識を広げることができます。

問題文:

マインスイーパは、1980 年代に人気を博したコンピュータ ゲームで、現在でも Microsoft Windows オペレーティング システムの一部のバージョンに含まれています。この問題も同様の考え方ですが、マインスイーパをプレイしたことがあるとは限りません。

この問題では、同じセルのグリッドでゲームをプレイしています。各セルの内容は、最初は非表示になっています。グリッドの M 個の異なるセルに M 個の地雷が隠されています。他のセルには地雷が含まれていません。任意のセルをクリックして表示できます。明らかにされたセルに鉱山が含まれている場合、ゲームは終了し、負けます。それ以外の場合、公開されたセルには、地雷を含む隣接セルの数に対応する 0 から 8 までの数字が含まれます。2 つのセルがコーナーまたはエッジを共有している場合、2 つのセルは隣接しています。さらに、公開されたセルに 0 が含まれている場合、公開されたセルのすべての隣接セルも再帰的に自動的に公開されます。地雷を含まないすべてのセルが表示されると、ゲームは終了し、勝利します。

たとえば、ボードの初期設定は次のようになります (「*」は鉱山を示し、「c」は最初にクリックされたセルです)。

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

クリックされたセルに隣接する地雷がないため、表示されると 0 になり、隣接する 8 つのセルも表示されます。このプロセスが続き、次のボードが作成されます。

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

この時点で、地雷を含まない未公開のセル (「.」文字で示される) がまだあるため、プレイヤーはゲームを続行するためにもう一度クリックする必要があります。

できるだけ早くゲームに勝ちたい。ワンクリックで勝つことほど速いものはありません。ボードのサイズ (R x C) と隠された地雷の数 M を考えると、1 回のクリックで勝つことは可能 (可能性は低いですが) でしょうか? クリックする場所を選択できます。可能であれば、出力セクションの仕様に従って、有効な地雷の構成とクリックの座標を印刷します。それ以外の場合は、「不可能」と出力します。

私の試した解決策:

したがって、解決策として、各非鉱山ノードが他の非鉱山ノードと一緒に 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) をクリックすると 1 クリックで解決できます。

私のソリューションはほとんどのケースで機能しますが、提出に合格しませんでした (225 ケースの出力ファイル全体を確認しました)。

4

11 に答える 11

24

アルゴリズム

Nまず、非鉱山セルの数 を定義しましょう。

N = R * C - M

N簡単な解決策は、上から下に行ごとに非鉱山セルの領域を埋めることです。R=5C=5、 の例M=12:

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

あれは:

  • 常に左上隅から開始します。
  • N / C行を上から下に非地雷で埋めます。
  • N % C次の行を左から右に非地雷で埋めます。
  • 残りを地雷で埋めます。

注意が必要な特殊なケースがいくつかあります。

単一の非鉱山

の場合N=1、どの構成も正しいソリューションです。

1 行または 1 列

の場合は、地雷以外を左から右にR=1記入してください。Nの場合、行を(単一の)非鉱山C=1で埋めます。N

非鉱山が少なすぎる

が偶数の場合N、>= 4 でなければなりません。

Nが奇数の場合、>= 9 でなければなりません。また、 RandCは >= 3 でなければなりません。

そうでなければ、解決策はありません。

最初の 2 行を埋めることができません

Nが偶数で、少なくとも 2 行を非地雷で埋めることができない場合は、最初の 2 行をN / 2非地雷で埋めます。

Nが奇数で、少なくとも 2 行を非地雷で埋め、3 行目を 3 つの非地雷で埋められない場合、最初の 2 行を非(N - 3) / 2地雷で埋め、3 行目を 3 つの非地雷で埋めます。

最後の行の単一の非鉱山

の場合N % C = 1、最後の完全な行から次の行に最後の非鉱山を移動します。

R=5C=5、 の例M=9:

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

概要

これらのルールを実装し、結果として生じる地雷原の説明を に返すアルゴリズムを作成することができますO(1)O(R*C)もちろん、グリッドの描画には がかかります。また、これらのアイデアに基づいて Perl で実装を作成し、Code Jam Judge に受け入れられました。

于 2014-04-13T12:24:23.950 に答える
15

この問題には、小規模なテスト ケースと大規模なテスト ケースの両方に合格する、より一般的な解決策があります。すべての特殊なケースを考える必要がなくなり、ボードの寸法は気にせず、バック トラッキングも必要ありません。

アルゴリズム

基本的な考え方は、地雷でいっぱいのグリッドから始めて、ボード上に正しい数の地雷ができるまで、セル {0, 0} からそれらを削除することです。

明らかに、次に除去する地雷と、正しい数の地雷を除去することが不可能な時期を判断する何らかの方法が必要です。これを行うためにint[][]、ボードを表す を保持できます。地雷のある各セルには含まれ、-1地雷のないセルには、セルに隣接する地雷の数である整数が含まれます。実際のゲームと同じ。

また、ゼロ以外のすべての非地雷セル、つまり地雷が隣接しているセルである「フロンティア」の概念を定義します。

たとえば、構成:

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

次のように表されます。

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

そしてフロンティアには、次の値を持つセルが含まれます。2, 3, 5, 2

地雷を除去するときの戦略は次のとおりです。

  • 除去する地雷の残りの数と同じ値を持つフロンティアのセルを見つけます。したがって、上記の例で、除去する地雷があと 5 つある場合は、フロンティアの値が 5 のセルが選択されます。
  • 最小のフロンティア セルを選択できなかった場合。したがって、上記の例の 2 のいずれかです。
  • 選択したセルの値が、取り除かなければならない地雷の数よりも大きい場合、このボードを構築することは不可能であるため、false を返します。
  • それ以外の場合は、選択したフロンティア セルを取り囲むすべての地雷を取り除きます。
  • ボード上に正しい数の地雷が存在するまで繰り返します - 問題の制約が満たされています。

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;
}

証明 / 直感

まず、1 回のクリックで解けるボードは有効であると定義されます。これは、ボード上でこのクリックが発生する可能性があるセルが 1 つしかない場合でも同様です。したがって、ボードが有効であるためには、すべての非鉱山セルが値 0 のセルに隣接している必要があります。そうでない場合、セルはunreachableとして定義されます。これは、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 キューを保持することで実装できますが、最初に思ったよりも難しいことにすぐに気付きました。これは、フロンティア セルの値が相互に依存しているため、フロンティア セルのコレクションを正しい状態に保ち、セルの値をいかなる種類のハッシュ値などにも使用しないように注意する必要があるためです。これは可能だと確信していますが、余分なヒューリスティックを追加して、残りの削除と等しい値を持つセルを選択するだけでうまくいくことに気付いたとき、これはより簡単なアプローチのように思えました.

于 2014-04-15T10:36:43.510 に答える
3

これは私のコードです。number of rows=1ifまたはnumber of columns=1or ifのようなさまざまなケースnumber of mines=(r*c)-1と、他のいくつかのケースを解決しました。

クリックするレイアウト上の位置は、a[r-1][c-1]毎回 ('0' インデックス) に配置されます。

この質問のために、私はいくつかの間違った試みをしましたが、そのたびに新しいケースを見つけ続けました. 使用して解決できないいくつかのケースを排除しgoto、印刷が不可能なところまでジャンプさせました。非常に単純な解決策です(個別に可能なさまざまなケースをコーディングしたため、実際には力ずくの解決策と言えます)。これは私のコードの社説です。そしてgithubで。

于 2014-04-13T12:33:35.137 に答える
2

私の戦略はあなたの戦略と非常に似ていて、小規模と大規模の両方に合格しました。以下のケースについて考えましたか?

  • R * C - M = 1

  • 1列しかない

  • 2列しかない


R > C の場合、R と C を反転させました。

于 2014-04-13T11:37:44.323 に答える
1

この問題に対する私のアプローチは次のとおりです。

  • 1x1 グリッドの場合、M はゼロでなければなりません。それ以外の場合は不可能です
  • Rx1 または 1xC グリッドの場合、M <= R * C - 2 が必要です (最後のセルに「c」を配置し、その隣に空のセルを配置します)。
  • RxC グリッドの場合、M <= R * C - 4 が必要です (「c」を角に配置し、その周りに 3 つの空のセルを配置します)。

要約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 の社説を読んで、自分のソリューションの問題点を理解しました。マインスイーパの基本的なルールについて説明する必要がありました。

于 2014-04-14T10:30:18.637 に答える
1

コード 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};
        }
    }
于 2014-04-14T03:55:38.463 に答える
0

この質問でも運試しをしましたが、何らかの理由でチェックに合格しませんでした。

"c" とその境界 "." に必要なセルは 4 つだけだったので、(rows*cols-4) 未満の地雷があれば (3x3 行列) で解けると考えました。

私のアルゴリズムは次のとおりです。

解ける?:

  1. 地雷用の十分なスペースがあるかどうかを確認します ( rows*cols - 4 == maximum mines)
  2. 行 == 1、列 == 1 のような例外。次に、rows*cols-2 です
  3. 可能か不可能かの条件付き

ビルド ソリューション

  1. Build rows*cols matrix、デフォルト値は nil
  2. に移動しm[0][0]て割り当てます'c'
  3. m[0][0]で周囲を定義する'.'
  4. マトリックスの右下からループし'*'、地雷がなくなるまで割り当ててから割り当てます'.'
于 2014-04-13T06:17:53.513 に答える
0

解決策はここにあります。以下ページの目次。

有効な鉱山構成を生成する方法はたくさんあります。この分析では、考えられるすべてのケースを列挙し、各ケースに対して有効な構成を生成しようとします (存在する場合)。後で、ある程度の洞察を得た後、有効な鉱山構成 (存在する場合) を生成するための実装がより簡単なアルゴリズムを提供します。

考えられるすべてのケースを列挙する

些細なケースをチェックすることから始めます。

空のセルが 1 つしかない場合は、クリックしたセルを除くすべてのセルを地雷で埋めることができます。R = 1 または C = 1 の場合、地雷はそれぞれ左から右または上から下に配置でき、それぞれ一番右または一番下のセルをクリックします。ボードが上記の 2 つの些細なケースに当てはまらない場合は、ボードのサイズが少なくとも 2 x 2 であることを意味します。次に、次のことを手動で確認できます。

空のセルの数が 2 または 3 の場合、有効な構成を持つことは不可能です。R = 2 または C = 2 の場合、有効な構成は M が偶数の場合にのみ存在します。たとえば、R = 2、C = 7、M = 5 の場合、M は奇数なので不可能です。ただし、M = 6 の場合、次のようにボードの左側に地雷を配置し、右下をクリックすることができます: *.... * ...c ボードが上記のいずれにも該当しない場合、それはボードが少なくとも 3 x 3 サイズであることを意味します。この場合、空のセルの数が 9 より大きい場合、常に有効な鉱山構成を見つけることができます。これを行う 1 つの方法を次に示します。

空のセルの数が 3 * C 以上の場合、地雷を上から下に 1 行ずつ配置できます。残りの地雷の数が列を完全に埋めることができるか、C - 2 未満の場合、その列に左から右に地雷を配置します。それ以外の場合、残りの地雷の数はちょうど C - 1 で、最後の地雷を次の行に配置します。例えば: ****** ****** *****。****.. ...... -> *..... ...... ...... .....c .....c 空のセルの数の場合は 3 * C 未満ですが、少なくとも 9 の場合、最初に最後の 3 行を除くすべての行を地雷で埋めます。最後の 3 行については、残りの地雷を左端の列から列ごとに埋めていきます。最後の列の残りの地雷が 2 個の場合、次に、最後の鉱山を次の列に配置する必要があります。例えば: ****** ****** .... -> * ... **.... *..... *....c *....c ここで、最大で 9 個の空のセルが残ります。右下隅にある 3 x 3 の正方形のセル。この場合、空のセルの数が 5 または 7 の場合、有効な鉱山構成を持つことは不可能であることを手動で確認できます。それ以外の場合は、その 3 x 3 の正方形セル内の空のセルの数ごとに有効な構成をハードコーディングできます。

はぁ…カバーするケースが多かった!ソリューションをコーディングするときに、コーナー ケースを見逃さないことをどのように確信させるのでしょうか?

力ずくのアプローチ

小さな入力の場合、ボードのサイズは最大で 5 x 5 です。考えられるすべての鉱山構成 (25 個から M を選択) をチェックして、有効なものを見つけることができます (つまり、構成内の空のセルをクリックすると、他のすべての空のセルが表示されます)。鉱山の構成が有効かどうかを確認するには、クリックされた空のセルからフラッド フィル アルゴリズム (または単純な息優先検索) を実行し、他のすべての空のセルが到達可能であること (つまり、それらが 1 つの接続されたコンポーネント内にあること) を確認します。 . 可能なすべてのクリック位置も確認する必要があることに注意してください。この力ずくのアプローチは、小さな入力に対して十分に高速です。

ブルート フォース アプローチを使用して、(R、C、M の値が小さい場合) 上記の列挙戦略に偽陰性があるかどうかを確認できます。有効な鉱山構成が存在する場合、偽陰性が見つかりますが、上記の列挙戦略では不可能となります。列挙戦略が偽陰性を生成しないことを確信したら、それを使用して大きな入力を解決できます。

より簡単に実装できるアプローチ

上記の列挙戦略を使用していくつかの有効な地雷構成を試した後、パターンに気付くかもしれません: 有効な地雷構成では、特定の行の地雷の数は常にその下の行の地雷の数と同じかそれ以上であり、すべての地雷が一列に左揃えになっています。この洞察により、現在の行の構成が無効な場合は次の行に入力し、プルーニングに進むにつれて、地雷の数を増加させずに上から下に行ごとに地雷を配置する、より単純なバックトラッキング アルゴリズムを実装できます (右下のセルをクリックすると確認できます)。このプルーニングによるバックトラックは、妥当な時間で最大 50 x 50 サイズのボードを処理でき、実装がより簡単です (つまり、コーナー/トリッキーなケースを列挙する必要はありません)。

コンテスト時間が短かった場合、考えられるすべてのケースを列挙するのに十分な時間がない可能性があります。この場合、バックトラッキング アルゴリズム (または実装がより簡単な他のアルゴリズム) に賭けることは良い考えかもしれません。そのようなアルゴリズムを見つけることは芸術です:)。

于 2015-08-03T01:51:58.223 に答える