在二维矩阵中找不到任何可能形成的炸药地雷,其中某些单元格包含与它们相邻的偶数/奇数地雷的信息

时间:2019-03-05 11:49:45

标签: algorithm matrix data-structures dynamic-programming puzzle

我正在尝试制作涉及2D网格的游戏,其中给出了一些提示,玩家可以避免包含爆炸性地雷的细胞。我遇到了一种特殊情况,其中给出了某些提示,我想知道可能有多少枚地雷。

让我们有一个2D矩阵。每个牢房可能是空的,也可能包含爆炸性地雷。 每个单元都有一些信息。如果单元格的值为

  • “ E”:这意味着即使与该单元格相邻的单元格也没有地雷。
  • 'O':表示与该单元格相邻的单元格中没有地雷。
  • 'N':表示不存在'E'或'O'值。它并不能说明周围的环境及其本身。

下面给出的二维矩阵示例:
N N
N N
所有可能的阵型都不是16。
O N
O N
O E
所有可能的形式都不是4。

这些是我手工计算的值。我一直坚持制作一个有效的程序来计算网格尺寸

的所有可能形式

3 个答案:

答案 0 :(得分:2)

基本上,您必须求解 Z / 2以上的方程组。实际上,这与玩名为Lights Out的游戏非常相似。让我们以这个板为例。

O N
O N
O E

让我们为不同的电路板位置创建变量。

x11 x12
x21 x22
x31 x32

我们得到这样的方程式。每个O变成一个(sum of neighbor variables) = 1 (mod 2)之类的方程。每个E都变成一个(sum of neighbor variables) = 0 (mod 2)之类的方程。

x12 + x21       = 1 (mod 2)
x11 + x22 + x31 = 1 (mod 2)
      x21 + x32 = 1 (mod 2)        x22 + x31 = 0 (mod 2)

使用在 Z / 2上的高斯消除将这些方程式放在row echelon form中。 Z / 2很有趣,因为加和减之间没有区别。简而言之,我们反复选择出现在某个方程式中的变量,然后将该方程式添加到其他包含该变量的方程式中,然后将该方程式放在一边。我会示范。

x12 + x21 = 1
x11 + x22 + x31 = 1
x21 + x32 = 1
x22 + x31 = 0
----

为使事情变得有趣,我们在x21中选择x12 + x21 = 1

x11 + x22 + x31 = 1
(x21 + x32) + (x12 + x21) = (1 + 1) ==> x12 + x32 = 0
x22 + x31 = 0
----
x12 + x21 = 1

请注意,x21 + x211 + 1都简化为0,因为我们正在使用mod 2。现在让我们在x22中选择x11 + x22 + x31 = 1

x12 + x32 = 0
(x22 + x31) + (x11 + x22 + x31) = (0 + 1) ==> x11 = 1
----
x12 + x21 = 1
x11 + x22 + x31 = 1

我们尚未抛开的方程式中的所有变量都是不同的,因此接下来的两个步骤很无聊。

----
x12 + x21 = 1
x11 + x22 + x31 = 1
x12 + x32 = 0
x11 = 1

我们有4个独立方程,所以答案是2^(3*2 - 4) = 4个解决方案(通常为2^(board squares - equations))。有点无聊的结果,但这就是事实。

当我们简化方程式时,可能会发生两个有趣的事情。让我们考虑以下板。

E E
E E

我们得到以下方程式。

x12 + x21 = 1        x11 + x22 = 1
x11 + x22 = 1        x12 + x21 = 1

现在,让我们减少。

x12 + x21 = 1
x11 + x22 = 1
x11 + x22 = 1
x12 + x21 = 1
----

x11 + x22 = 1
x11 + x22 = 1
(x12 + x21) + (x12 + x21) = (1 + 1) ==> 0 = 0
----
x12 + x21 = 1

(x11 + x22) + (x11 + x22) = (1 + 1) ==> 0 = 0
0 = 0
----
x12 + x21 = 1
x11 + x22 = 1

我们最终得到两个简并方程0 = 0。这意味着我们给出了多余的信息,并且它们不算作独立的方程式。答案还是2^(2*2 - 2) = 4

可能发生的另一件事是我们得到方程0 = 1。在这种情况下,没有与提示一致的解决方案。

答案 1 :(得分:1)

结果证明,对于暴力破解来说,这应该还不错,至少在您的面板不是太大的情况下。

您可以定义炸弹组,其中每个单元格可以是PresentNot PresentUnexplored,其中Unexplored表示可能有或没有炸弹那里。然后,您可以编写一种方法来获取您的棋盘和这组炸弹,并根据任何Unexplored单元格的实际值来确定棋盘是否绝对有效或无效。

然后,您开始在木板上行走。将第一个单元格设置为Present,然后查看是否会导致板子有效(或绝对无效)。如果有效,则将递归设置为下一个单元格。然后将第一个单元格设置为NotPresent,看看那个是否有效,以及是否递归到下一个单元格。

与成熟的蛮力相比,对面积较小且无效的木板的修剪应大大减少了搜索空间。

在检查电路板是否有效时,可以仅通过检查所更改的单元格周围的正方形中的单元格来进行优化,因为这些单元格是唯一可能受影响的单元格。

这不是完整的动态编程,可能会受益于一些备忘:如果右下角有炸弹组合无效(右下角是最后探索的区域),它将继续尝试它们一次又一次地使用其他地方的不同(有效)炸弹组合。

如果您的木板有很大的开放区域,这也会陷入困境,因为会有大量的组合,并且会仔细地探索每个组合。

我聚集了一些C#来说明我的想法。它不是很整洁,也不是特别清楚(为此,我很抱歉,我没有时间整理一下),但是它为您的第二个示例找到了4个解决方案。

这是使用递归编写的,因此会用较大的电路板烧毁堆栈。将其重写为可迭代的。

class Program
{
    static void Main(string[] args)
    {
        var board = new Board(new CellState[,]
        {
            { CellState.Odd, CellState.None },
            { CellState.Odd, CellState.None },
            { CellState.Odd, CellState.Even }
        });

        var bombs = new BombState[board.Height, board.Width];
        int numSolutions = 0;

        Explore(board, bombs, 0, 0, ref numSolutions);
    }

    private static void Explore(Board board, BombState[,] bombs, int x, int y, ref int numSolutions)
    {
        int nextX = x + 1;
        int nextY = y;
        if (nextX >= board.Width)
        {
            nextX = 0;
            nextY++;
        }

        bombs[y, x] = BombState.Present;
        if (board.DetermineValidity(bombs, x, y))
        {
            if (nextY >= board.Height)
                numSolutions++;
            else
                Explore(board, bombs, nextX, nextY, ref numSolutions);
        }

        bombs[y, x] = BombState.NotPresent;
        if (board.DetermineValidity(bombs, x, y))
        {
            if (nextY >= board.Height)
                numSolutions++;
            else
                Explore(board, bombs, nextX, nextY, ref numSolutions);
        }

        bombs[y, x] = BombState.Unexplored;
    }
}

public enum CellState
{
    Odd,
    Even,
    None,
}

public enum BombState
{
    Unexplored,
    Present,
    NotPresent,
}

public class Board
{
    private readonly CellState[,] cells;
    public int Width { get; }
    public int Height { get; }

    public Board(CellState[,] cells)
    {
        this.cells = cells;
        this.Width = cells.GetLength(1);
        this.Height = cells.GetLength(0);
    }

    // Takes a board of bombs, and the position of a bomb to inspect, and determines
    // whether that bomb position is definitely valid, or is unknown/invalid
    public bool DetermineValidity(BombState[,] bombs, int changedX, int changedY)
    {
        // We only need to consider the cells in a square around the cell which was just changed

        for (int x = Math.Max(0, changedX - 1); x < Math.Min(this.Width, changedX + 1); x++)
        {
            for (int y = Math.Max(0, changedY - 1); y < Math.Min(this.Height, changedY + 1); y++)
            {
                var cellState = this.cells[y, x];

                // If this is a "None", there's nothing to check
                if (cellState == CellState.None)
                    continue;

                // For each cell, check its neighbours... If they're all specified, get the number of boms
                int numBombs = 0;
                bool areAllSpecified = true;

                if (x > 0)
                    InspectNeighbour(bombs[y, x - 1], ref numBombs, ref areAllSpecified);
                if (areAllSpecified && x < this.Width - 1)
                    InspectNeighbour(bombs[y, x + 1], ref numBombs, ref areAllSpecified);
                if (areAllSpecified && y > 0)
                    InspectNeighbour(bombs[y - 1, x], ref numBombs, ref areAllSpecified);
                if (areAllSpecified && y < this.Height - 1)
                    InspectNeighbour(bombs[y + 1, x], ref numBombs, ref areAllSpecified);

                if (areAllSpecified && ((numBombs % 2) == 0) != (cellState == CellState.Even))
                    return false;
            }
        }

        return true;
    }


    private static void InspectNeighbour(BombState state, ref int numBombs, ref bool areAllSpecified)
    {
        switch (state)
        {
            case BombState.NotPresent:
                break;
            case BombState.Present:
                numBombs++;
                break;
            case BombState.Unexplored:
                areAllSpecified = false;
                break;
        }
    }
}

答案 2 :(得分:1)

可爱的问题。听起来像是编程竞赛风格的问题。提示:

  

将此表示为GF(2)上的线性代数问题(即算术模2),然后使用高斯消去。

提示:

  

如果给定矩阵A和向量b,您可以计算方程$ Ax = b $的解数吗?怎么样?