炸弹丢弃算法

时间:2013-03-08 17:47:03

标签: algorithm language-agnostic matrix

我有一个由非负整数组成的n x m矩阵。例如:

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

“丢弃炸弹”将目标单元格及其所有八个邻居的数量减少一个,至少为零。

x x x 
x X x
x x x

什么算法可以确定将所有单元格减少到零所需的最小炸弹数量?

B选项(由于我不是一个细心的读者)

实际上问题的第一个版本并不是我想要回答的问题。我没有仔细阅读整个任务,还有其他限制,让我们说:

当行中的序列必须不增加时,简单问题怎么办:

8 7 6 6 5是可能的输入序列

7 8 5 5 2是不可能的,因为7 - > 8按顺序增长。

也许找到“更容易”的案例的答案将有助于找到更难的解决方案。

PS:我相信当我们有几个相同的情况需要最少的炸弹来清除上线时,我们选择在该行的“左侧”使用最多炸弹的一个。还有什么证据可能是正确的吗?

31 个答案:

答案 0 :(得分:38)

有一种方法可以将其减少为一个简单的子问题。

解释,算法和算法的原因分为两部分 提供最佳解决方案。没有第二个,第一个没有意义,所以我会 从为什么开始。

如果你想轰炸这个矩形(假设一个大矩形 - 还没有边缘情况) 你可以看到减少空心方块的唯一方法 周长为0是炸弹周边或轰炸空心矩形 在外围的正方形。我将调用周边层1,并将其中的矩形称为第2层。

一个重要的见解是,没有点轰炸第1层,因为 你从中获得的“爆炸半径”总是包含在爆炸半径范围内 来自第2层的另一个方格。你应该能够轻松地说服自己。

所以,我们可以减少问题,找到炸毁周界的最佳方法,然后我们可以重复这个,直到所有方格都为0。

但是,当然,如果有可能炸弹,那么这并不总能找到最佳解决方案 以不太理想的方式远离外围,但通过使用X额外的炸弹制造 通过> X炸弹减少内层的问题更简单。所以,如果我们打电话 许可证第一层,如果我们在第2层的某处放置一个额外的X炸弹(只是 在第1层内部,我们可以减少后来轰炸第2层的努力 X?换句话说,我们必须证明我们可以贪婪地减少外在 周长。

但是,我们知道我们可以贪婪。因为第2层中没有炸弹可以更多 有效地将第2层减少到0比第3层中策略性放置的炸弹 出于同样的原因 - 我们可以在第3层放置炸弹 将影响放置在第2层的炸弹可以影响第2层的每个方格。所以,它可以 永远不要伤害我们贪婪(在这种贪婪的意义上)。

因此,我们所要做的就是找到通过轰炸将许可证减少到0的最佳方法 下一个内层。

我们永远不会受到第一次轰炸角落到0的伤害,因为只有内层的角落可以到达它,所以我们别无选择(并且,周边任何可以到达角落的炸弹都有一个爆炸半径从内层角落包含在爆炸半径内。)

一旦我们这样做了,与0角相邻的周边的方块只能从内层到达2个方格:

0       A       B

C       X       Y

D       Z

此时周长实际上是一个封闭的一维环,因为任何炸弹都会减少3个相邻的方格。除了角落附近的一些奇怪之外 - X可以“击中”A,B,C和D.

现在我们不能使用任何爆炸半径技巧 - 每个方格的情况是对称的,除了怪异的角落,甚至没有爆炸半径是另一个的子集。请注意,如果这是一行(如Panic Panic所讨论的)而不是闭环,那么解决方案就是微不足道的。终点必须减少到0,并且永远不会让你轰炸终点附近的点,再次因为爆炸半径是一个超集。一旦你的端点为0,你仍然有一个新的端点,所以重复(直到该行全部为0)。

因此,如果我们能够将图层中的单个正方形最优地减少到0,那么我们就有了一个算法(因为我们已经切断了循环,现在有一个带有端点的直线)。我相信在广场附近的爆炸具有最低值(给你2个选项),使得最低值的2个方格中的最高值是最小可能(你可能必须拆分你的轰炸来管理这个)将是最佳的但我不(但?)有证据。

答案 1 :(得分:25)

Pólya说:“如果你无法解决问题,那么你可以解决一个更容易解决的问题:找到它。”

明显更简单的问题是一维问题(当网格是单行时)。让我们从最简单的算法开始 - 贪婪地轰炸最大的目标。什么时候出错?

鉴于1 1 1,贪婪算法对它首先轰炸的细胞无动于衷。当然,中心细胞更好 - 它同时将所有三个细胞归零。这表明一种新的算法A,“炸弹以最小化剩余的总和”。这个算法何时出错?

鉴于1 1 2 1 1,算法A在轰炸第2,第3或第4个细胞之间无动于衷。但轰炸第二个细胞离开0 0 1 1 1比轰炸第三个细胞离开1 0 1 0 1更好。如何解决?轰炸第三个细胞的问题在于它让我们向左移动并向右移动,必须单独完成。

怎么样“炸弹最大限度地减少剩余的数量,但最大化左边的最小值(我们轰炸的地方)加上右边的最小值”。调用此算法B.此算法何时出错?


编辑:在阅读完评论后,我同意一个更有趣的问题就是一维问题发生了变化,以便结局加入。很想看到任何进展。

答案 2 :(得分:12)

我不得不停止在部分解决方案,因为我没有时间,但希望即使这个部分解决方案提供一些解决这个问题的潜在方法的见解。

当面对一个难题时,我想提出更简单的问题来培养对问题空间的直觉。在这里,我采取的第一步是将这个二维问题简化为一维问题。考虑一条线:

0 4 2 1 3 0 1

不知怎的,你知道你需要在4点附近炸弹4次以使其降低到0。由于现场的左边是较低的数字,因此轰炸是没有好处的。 04轰炸2。事实上,我相信(但缺乏严格的证据)轰炸2直到4点降至0至少与使4降低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 的任何其他策略一样好在这样的策略中,可以从左到右继续前进:

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

一些样本爆炸命令:

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的唯一方法。更重要的是,找出最优策略是非常简单的(因为它在将角落数量降至0,我们把它们全部放在一起,可以更接近二维空间中的解决方案。

鉴于对角落件的观察,我们可以肯定地说,我们知道从任何起始板到各个角落都有零的电路板的最佳策略。这是这种电路板的一个例子(我借用了上面两个线性电路板的数字)。我用不同的方式标记了一些空格,我将解释原因。

x

人们会注意到顶行真的非常类似于我们之前看到的线性示例。回顾我们之前的观察结果,将顶行全部降为0的最佳方法是轰炸第二行(y行)。没有办法通过轰炸任何x行来清除顶行,并且在轰炸x行的相应空间时轰炸顶行没有额外的好处。

我们可以应用上面的线性策略(轰炸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 行上的相应空格),关于我们自己的顶行,没有别的。它会是这样的:

4

这种方法的缺陷在最后两次爆炸中变得非常明显。很明显,鉴于减少第二行第一列中x数字的唯一炸弹网站是第一个yx。最后两次爆炸明显不如仅仅轰炸第一次0 4 2 1 4 x y a 2 z . . 1 b . . ,这将完全相同(关于顶行的第一个位置,我们没有其他方式清除)。由于我们已经证明我们当前的策略不是最理想的,因此显然需要对策略进行修改。

此时,我可以在复杂性上退后一步,只关注一个角落。让我们考虑一下:

4

很明显,将x的空格降为零的唯一方法是轰炸yzx的某些组合。在我的脑海中有一些杂技,我相当确定最佳解决方案是将a炸弹三次,然后b然后y。现在问题是如何弄清楚我是如何达到这个解决方案的,如果它揭示了我们可以用来解决这个局部问题的任何直觉。我注意到z0 4 2 5 0 4 x y a . 2 z . . . 5 b . . . 0 . . . . 空格没有轰炸。试图找到一个轰炸那些空间的角落有意义产生一个看起来像这样的角落:

y

对于这一点,我很清楚,最佳解决方案是将z轰炸5次,0 4 2 5 6 0 0 4 x y a . . . 2 z . . . . . 5 b . . . . . 6 . . . . . . 0 . . . . . . 0 . . . . . . 5次。让我们更进一步。

a

在这里,感觉同样直观,最佳解决方案是将bx轰炸6次,然后{{1}} 4次。

现在,它变成了一种如何将这些直觉转化为我们可以建立的原则的游戏。

希望继续!

答案 3 :(得分:10)

对于更新的问题,一个简单的贪婪算法可以得到最佳结果。

将A [0,0]炸弹投放到单元A [1,1],然后将A [1,0]炸弹投放到单元A [2,1],并向下继续此过程。要清除左下角,请将最大值(A [N-1,0],A [N-2,0],A [N-3,0])炸弹丢弃到单元格A [N-2,1]。这将完全清理前3列。

使用相同的方法清理列3,4,5,然后清除列6,7,8等

不幸的是,这无助于找到原始问题的解决方案。


“更大”的问题(没有“非重复”约束)可能被证明是NP难的。这是一个证明的草图。

假设我们有一个度数最多为3的平面图。让我们找到该图的最小vertex cover。根据维基百科的文章,对于程度高达3的平面图,这个问题是NP难的。这可以通过平面3SAT的减少来证明。和平面3SAT的硬度 - 从3SAT减少。这两个证明都在教授的"Algorithmic Lower Bounds"最近的讲座中介绍。 Erik Demaine(讲座7和9)。

如果我们分割原始图形的一些边缘(图上的左图),每个边缘都有偶数个附加节点,结果图(图上的右图)应该具有与原始顶点完全相同的最小顶点覆盖。这种转换允许将图形顶点对齐到网格上的任意位置。

enter image description here

如果我们仅将图形顶点放置到偶数行和列(以这种方式使得没有两个边缘入射到一个顶点形成锐角),则在有边缘的地方插入“1”,并将“零”插入其他网格位置,我们可以使用原始问题的任何解决方案来找到最小顶点覆盖。

答案 4 :(得分:9)

这是一个部分答案,我试图找到一个下限和上限,可能是可能的炸弹数量。

在3x3和更小的电路板中,解决方案通常是最大编号的电池。

在大于4x4的电路板中,第一个明显的下限是角的总和:

*2* 3  7 *1*
 1  5  6  2
 2  1  3  2
*6* 9  6 *4*
然而,你安排炸弹,不可能用少于2 + 1 + 6 + 4 = 13的炸弹清除这个4x4板。

在其他答案中已经提到过,将炸弹放在第二个角落以消除角落并不比将炸弹放在角落本身更糟糕,所以给了董事会:

*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*

我们可以通过在第二个角落放置炸弹来使角落归零,从而得到一块新的棋盘:

 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

到目前为止一切顺利。我们需要13枚炸弹来清理角落。

现在观察下面标记的数字6,4,3和2:

 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

没有办法使用单个炸弹轰炸任何两个的那些细胞,所以最小炸弹增加了6 + 4 + 3 + 2,因此增加了我们使用的炸弹数量为了清除角落,我们得出这张地图所需的最小数量的炸弹已成为28枚炸弹。用不到28枚炸弹清除这张地图是不可能的,这是这张地图的下限。

您可以使用贪婪算法来建立上限。其他答案表明,贪婪算法产生的解决方案使用28枚炸弹。由于我们早先已经证明没有最佳解决方案可以少于28枚炸弹,因此28枚炸弹确实是最佳解决方案。

当贪婪和找到上面提到的最小边界的方法虽然没有收敛,但我猜你必须回去检查所有组合。

查找下限的算法如下:

  1. 选择编号最大的元素,将其命名为P.
  2. 将距离P和P本身两步远的所有单元格标记为不可用。
  3. 将P添加到minimums列表。
  4. 重复步骤1直到所有单元格都不可用。
  5. 汇总minimums列表以获得下限。

答案 5 :(得分:9)

您可以将此问题表示为integer programming问题。 (这只是解决这个问题的可能解决方案之一)

得分:

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

可以写出16个等式,其中f点例如成立

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

对所有索引和整数解的总和进行最小化。

解决方案当然是这些索引的总和。

通过在边界0上设置所有xi可以进一步简化这一点,因此在本例中最终得到4 + 1个等式。

问题在于解决这些问题没有琐碎的算法。我不是这方面的专家,但解决这个问题就像线性规划是NP难的。

答案 6 :(得分:9)

这将是一种贪婪的方法:

  1. 计算n×m阶的“得分”矩阵,其中得分[i] [j]是如果位置(i,j)被轰炸则矩阵中的点的总扣除。 (一个点的最高得分为9,最低得分为0)

  2. 逐行移动,找到并选择得分最高的第一个位置(比如说(i,j))。

  3. 炸弹(i,j)。增加炸弹数量。

  4. 如果原始矩阵的所有元素都不为零,则转到1。

  5. 我怀疑这是最佳解决方案。

    修改

    我上面发布的贪婪方法虽然有效,但很可能并没有给我们提供最佳解决方案。所以我想应该添加DP的一些元素。

    我认为我们可以同意,在任何时候,必须针对具有最高“得分”的位置之一(得分[i] [j] =如果(i,j)被轰炸的点总扣除) 。从这个假设开始,这是新方法:

    NumOfBombs(M):(返回所需的最小爆炸次数)

    1. 给定矩阵M的阶数为n X m。如果M的所有元素都为零,则返回0.

    2. 计算“得分”矩阵M.

      令k个不同位置P1,P2,... Pk(1 <= k <= n * m)是具有最高分数的M中的位置。

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

      如果我们分别轰炸位置P1,P2,......,Pk,那么M1,M2,...,Mk就是结果矩阵。

    4. 此外,如果我们希望除此之外还有核心位置,我们必须跟踪“min”的结果。

答案 7 :(得分:8)

您的问题(跨行的非减少值)很容易解决。

观察左列包含最高数字。因此,任何最佳解决方案必须首先将此列减少到零。因此,我们可以对此列执行 1-D 轰炸,将其中的每个元素减少为零。我们让炸弹落在第二列上,这样它们就会造成最大的伤害。我认为,这里有很多关于1D案例的帖子,所以我觉得在跳过这个案子时我是安全的。 (如果你想让我描述一下,我可以。)由于性能下降,最左边的三列将全部减少到零。但是,我们可以在这里使用最少数量的炸弹,因为左列必须归零。

现在,一旦左列归零,我们只需修剪现在归零的三个最左边的列,并用现在减少的矩阵重复。这必须为我们提供最佳解决方案,因为在每个阶段我们都使用可证明最少数量的炸弹。

答案 8 :(得分:3)

使用分支定界的Mathematica整数线性规划

正如已经提到的,这个问题可以使用整数线性编程(NP-Hard)来解决。 Mathematica已经内置了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." [参见Mathematica中的Constrained Optimization教程..]

我编写了以下使用Mathematica的ILP库的代码。它出人意料地快。

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

对于问题中提供的示例:

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]

输出

enter image description here

对于任何使用贪心算法阅读此内容的人

尝试使用以下10x10问题的代码:

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

这是逗号分隔:

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

对于这个问题,我的解决方案包含 208 炸弹。这是一个可能的解决方案(我能够在大约12秒内解决这个问题)。

enter image description here

作为测试Mathematica正在生成的结果的一种方法,看看你的贪婪算法能否做得更好。

答案 9 :(得分:3)

似乎线性编程方法在这里非常有用。

P m x n 成为具有位置值的矩阵:

Matrix of positions

现在让我们定义一个炸弹矩阵B(x,y) mxn 1≤x≤m1≤ y≤n如下

Bomb matrix

以这样的方式

Values of positions in bomb matrix

例如:

B(3, 3)

所以我们期待矩阵 B m x n = [ b ij ]

  1. 可以定义为炸弹矩阵的总和:

    B as a sum of bomb matrices

    q ij 然后是炸弹的数量我们将落在位置 p ij

  2. p ij - b ij ≤0(更多的是,我们将其称为 P - B≤0

  3. 此外, B 应最小化总和sum of quantities of bombs

    我们也可以将 B 写成前面的丑陋矩阵:

    B as a matrix of sum of quantities

    由于 P - B≤0(意味着P≤B),我们在下面有以下非线性不等式系统:

    Relationship between number of bombs dropped and values in positions

    q mn x 1 定义为

    Vector of quantities

    p mn x 1 定义为

    Values of P distributed as a vector

    我们可以说我们有一个系统The system below represented as product of matrices http://latex.codecogs.com/gif.download?S%5Cmathbf%7Bq%7D&space;%5Cge&space;%5Cmathbf%7Bp%7D S mn x mn 要反转的矩阵来解决系统问题。我自己没有扩展它,但我相信它应该很容易在代码中完成。

    现在,我们有一个最小的问题,可以说是

    The system we have to solve

    我相信用simplex algorithm(有this rather cool doc about it)之类的东西来解决它是一件容易的事,几乎是微不足道的。但是,我确实知道几乎没有线性编程(我会在Coursera上讲一个关于它的课程,但它只是在将来...),我有一些头痛试图理解它,我有一个巨大的自由职业完成所以我放弃这里可能是我在某些时候做错了,或者它不能再进一步了,但我相信这条道路最终会导致 解决方案。无论如何,我很想得到你的反馈。

    (特别感谢this amazing site to create pictures from LaTeX expressions

答案 10 :(得分:3)

这通过这个“迷宫”的位置广泛搜索最短路径(一系列爆炸)。不,我无法证明没有更快的算法,抱歉。

#!/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 __name__ == '__main__':
  import sys
  sys.exit(main(sys.argv))

答案 11 :(得分:3)

无需将问题转换为线性子问题。

而是使用一个简单的贪婪启发式方法,即轰炸角落,从最大的一个开始。

在给定的示例中,有四个角,{2,1,6,4}。对于每个角落来说没有比将角落对角线炸到角落更好的移动,所以我们知道我们的第一次2 + 1 + 6 + 4 = 13次爆炸必须在这些对角线单元中。在进行轰炸之后,我们留下了一个新的矩阵:

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 

在前13次爆炸后,我们使用启发式通过三次爆炸消灭3 0 2。现在,我们在第4排有2个新角,{2,1}。我们轰炸那些,另外3次爆炸。我们现在将矩阵缩小到4 x 4。左上角有一个角落。我们炸弹了。现在我们剩下2个角,{5,3}。由于5是最大的角落,我们首先轰炸5次轰炸,然后最后轰炸另一个角落的3个。总数为13 + 3 + 3 + 1 + 5 + 3 = 28。

答案 12 :(得分:3)

这个贪婪的解决方案似乎是正确的

正如评论中所指出的,它将在2D中失败。但也许你可以改进它。

对于1D:
   如果至少有2个数字你不需要向最左边的数字射击,因为射击到第二个并不差。所以拍到第二个,而第一个不是0,因为你必须这样做。移动到下一个单元格。不要忘记最后一个细胞。

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])
    }
}

对于2D:
  再说一遍:你不需要在第一排拍摄(如果有第二排)。所以拍到第二个。解决第一行的1D任务。 (因为你需要使它为null)。下去。不要忘记最后一行。

答案 13 :(得分:2)

我相信,为了尽量减少炸弹的数量,你只需要最大限度地减少伤害。 为了实现这一点,需要检查具有最强力的区域。所以你首先用3x3内核分析该区域并检查总和更强的位置......并在那里炸弹......直到该区域是平的...这个答案是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');

答案 14 :(得分:2)

为了尽量减少炸弹的数量,我们必须最大限度地发挥每一枚炸弹的效果。要实现这一目标,我们必须在每一步中选择最佳目标。对于每个点总和它和它的八个邻居 - 可以用作轰炸这一点的效率数量。这将提供接近最佳的炸弹序列。

UPD :我们还应该考虑零个数,因为炸弹它们效率低下。实际上问题是最小化命中零的数量。但我们无法知道如何让我们更接近这一目标。我同意这个问题是NP完全的。我采用贪婪的方法,这将给出一个接近真实的答案。

答案 15 :(得分:2)

这是一个可以概括角落良好属性的解决方案。

让我们假设我们可以找到给定字段的完美丢弃点,也就是减少其中值的最佳方法。然后要找到要删除的最小炸弹数量,算法的初稿可以是(代码是从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

挑战是choose_a_perfect_drop_point。首先,让我们来定义一个完美的下降点。

  • (x, y)放置点会降低(x, y)中的值。它也可能会降低其他单元格中的值。
  • (x, y)的放置点 a b 的单元格的正确超集中的值减少。
  • 如果没有其他更好的丢弃点,则放置点 maximal
  • (x, y)的两个丢弃点是等效,如果它们减少了同一组单元格。
  • (x, y)的放置点完美,如果它等同于(x, y)的所有最大放置点。

如果(x, y)有完美的丢弃点,则无法在(x, y)处更有效地降低值,而不是将炸弹放在(x, y)的一个完美丢弃点上。

给定场的完美落点是其任何细胞的完美落点。

以下是一些例子:

(x, y)

单元格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 (从零开始的索引)的完美丢弃点是(0, 0)(1, 1)的所有其他放置点,即(1, 1)(0, 0)(0, 1),会减少较少的单元格。

(1, 0)

单元格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 (从零开始的索引)的完美放置点为(2, 2),以及所有周围单元格(2, 2)(1, 1),{{1} },(1, 2)(1, 3)(2, 1)(2, 3)(3, 1)

(3, 2)

单元格(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 :它会降低(2, 2)中的值和(3, 1)中的值。 (2, 2)的所有其他丢弃点都不是最大值,因为它们减少了一个单元格。 (4, 0)的完美下降点也是(2, 2)的完美下降点,它是该领域唯一完美的下降点。它导致了这个领域的完美解决方案(一次炸弹)。

(2, 2)

(4, 0)没有完美的丢弃点: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 (2, 2)都会减少(1, 1)和另一个单元格(它们是(1, 3)的最大丢弃点) ,但它们并不等同。但是,(2, 2)(2, 2)的完美丢弃点,(1, 1)(0, 0)的完美丢弃点。

通过完美丢弃点的定义和特定的检查顺序,我在问题中得到以下结果:

(1, 3)

但是,只有在每个步骤后至少有一个完美丢弃点时,该算法才有效。可以构建没有完美丢弃点的示例:

(0, 4)

对于这些情况,我们可以修改算法,以便我们选择一个具有最小最大丢弃点选择的坐标,而不是完美的放置点,然后计算每个选择的最小值。在上面的情况中,具有值的所有单元格具有两个最大丢弃点。例如,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 具有最大丢弃点0 1 1 0 1 0 0 1 1 0 0 1 0 1 1 0 (0, 1)。选择一个然后计算最小值会导致这个结果:

(1, 1)

答案 16 :(得分:2)

这是另一个想法:

让我们首先为棋盘上的每个空间分配一个重量,以便通过在那里放下炸弹来减少多少数量。因此,如果空间具有非零数字,则它获得一个点,并且如果与其相邻的任何空间具有非零数字,则它获得额外的点。因此,如果存在1000×1000的网格,我们将为100万个空间中的每一个分配权重。

然后按重量对空格列表进行排序,然后轰炸重量最大的空间列表。对我们来说,这是最大的收获,可以这么说。

之后,更新重量受炸弹影响的每个空间的重量。这将是您被轰炸的空间,以及与其紧邻的任何空间,以及与其紧邻的任何空间。换句话说,任何可能通过轰炸将其值减小到零的空间,或者相邻空间的值减少到零。

然后,按重量重新排序列表空间。由于轰炸只改变了一小部分空间,所以你不需要求助整个列表,只需在列表中移动它们。

轰炸新的最高重量空间,并重复该过程。

这保证了每次轰炸都会减少尽可能多的空间(基本上,它会尽可能少地占据已经为零的空间),因此它是最佳的,除了它们可以是重量的关系。所以你可能需要在顶部重量达到平局时进行一些反向追踪。然而,只有顶级重量并不重要,而不是其他关系,所以希望它没有太多回溯。

修改 下面的Mysticial的反例表明,无论权重的关系如何,这都不能保证是最优的。在某些情况下,在给定的步骤中尽可能地减轻重量实际上使剩余的炸弹分散得太多以在第二步之后实现高的累积减少,因为在第一步中可以选择稍微不那么贪婪的选择。我认为结果对爆炸的顺序不敏感,这有点误导。他们 对订单不敏感,因为你可以采取任何系列的爆炸并从一开始就以不同的顺序重播它们,最终得到相同的结果板。但是,并不是因为你可以独立考虑每次轰炸。或者,至少每次轰炸都必须考虑到它为后续轰炸设置板的能力。

答案 17 :(得分:1)

好吧,假设我们将电路板位置编号为1,2,...,n x m。任何一系列炸弹都可以用这组中的一系列数字来表示,其中数字可以重复。然而,无论你投掷炸弹的顺序如何,对棋盘的影响都是一样的,所以真正任何炸弹掉落的选择都可以表示为nxm数字的列表,其中第一个数字代表位置1上掉落的炸弹数量,第二个数字表示在第2个位置投下的炸弹数量,等等。让我们将这个nxm数字列表称为“关键”。

你可以先尝试计算1次炸弹掉落所产生的所有电路板状态,然后用它们来计算2次炸弹掉落等导致的所有电路板状态,直到你得到全零。但是在每一步中你都会使用我在上面定义的键来缓存状态,因此你可以使用这些结果来计算下一步(“动态编程”方法)。

但是根据n,m的大小和网格中的数字,这种方法的内存要求可能过高。一旦计算出N + 1的所有结果,就可以丢弃N个炸弹掉落的所有结果,因此可以节省一些费用。当然,你不能以花费更多 的代价来缓存任何东西 - 动态编程方法将内存换成速度。

答案 18 :(得分:1)

评估函数,总和:

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

目标函数:

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

销毁功能:

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

目标函数:

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

线性最大化函数:

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

这不是最优的,但可以通过找到更好的评估函数进行优化。

..但是考虑到这个问题,我认为其中一个主要问题是在某些时候在零点中间放弃被忽略的数字,所以我采取另一种方法......这是将最小值控制为零,然后尝试尽可能地逃避零,这导致一般最小化现有的最小值

答案 19 :(得分:1)

这可以使用深度为O(3 ^(n))的树来解决。其中n是所有正方形的总和。

首先考虑使用O(9 ^ n)树解决问题是微不足道的,只需考虑所有可能的轰炸位置。有关示例,请参阅Alfe's implementation

接下来意识到我们可以自下而上地进行轰炸,并且仍然可以获得最小的轰炸模式。

  1. 从左下角开始。
  2. 用唯一有意义的戏剧(向上和向右)将其炸成遗忘。
  3. 向右移动一个方格。
  4. 当目标的值大于零时,请考虑有意义的两个游戏中的每一个(向上或向上和向右),将目标值减一,并为每种可能性创建一个新分支
  5. 向右移动另一个。
  6. 当目标的值大于零时,请考虑3个有意义的游戏(向左,向上和向右),将目标值减1,并为每个可能性创建一个新分支
  7. 重复步骤5和6,直到删除该行。
  8. 向上移动一行并重复步骤1到7,直到拼图解决。
  9. 这个算法是正确的,因为

    1. 有必要在某一点完成每一行。
    2. 完成一行总是要求在上方,下方或该行内播放。
    3. 选择一个位于最低未清除行之上的游戏总是与行上或行下方的游戏一样好或更好。
    4. 在实践中,该算法通常会比理论上的最大值更好,因为它会定期轰炸邻居并减小搜索的大小。如果我们假设每次轰炸减少4个额外目标的值,那么我们的算法将在O(3 ^(n / 4))或大约O(1.3 ^ n)中运行。

      因为这个算法仍然是指数的,所以限制搜索的深度是明智的。我们可能会将允许的分支数量限制为某个数字X,一旦我们这么深,我们就会强制算法选择到目前为止已经识别的最佳路径(在其中一个终端叶子中具有最小总板数的那个) )。然后我们的算法保证在O(3 ^ X)时间运行,但不能保证得到正确的答案。但是,如果增加计算和更好的答案之间的权衡是值得的,我们总是可以增加X并进行经验测试。

答案 20 :(得分:1)

这里似乎有一个非二元匹配的子结构。请考虑以下实例:

0010000
1000100
0000001
1000000
0000001
1000100
0010000

这种情况的最佳解决方案的大小为5,因为它的边缘是9个周期的顶点的最小覆盖的大小。

特别是这种情况表明,一些人发布的线性编程放松并不准确,不起作用,以及所有其他不好的事情。我很确定我可以减少“覆盖我的平面立方图的顶点尽可能少的边缘”来解决你的问题,这让我怀疑是否有任何贪婪/爬山解决方案能够正常工作。

在最坏的情况下,我没有看到在多项式时间内解决这个问题的方法。可能有一个非常聪明的二进制搜索和DP解决方案,我没有看到。

编辑:我认为比赛(http://deadline24.pl)与语言无关;他们发送一堆输入文件,然后发送输出。因此,您不需要在最坏情况多项式时间运行的东西。特别是,您可以查看输入

输入中有一堆小案例。然后有一个10x1000的外壳,一个100x100的外壳和一个1000x1000的外壳。三个大案件都表现得非常好。水平相邻的条目通常具有相同的值。在一台相对强大的机器上,我能够在几分钟内使用CPLEX强制解决所有情况。我很幸运1000x1000; LP松弛碰巧有一个完整的最优解决方案。我的解决方案同意测试数据包中提供的.ans文件。

我敢打赌,你可以以比我看过它更直接的方式使用输入的结构;看起来你可以反复削减第一排,或两次,或三次,直到你什么也没有留下。 (看起来,在1000x1000中,所有的行都没有增加?我猜你的“B部分”来自哪里?)

答案 21 :(得分:1)

我想不出一种计算实际数字的方法,而不仅仅使用我最好的启发式计算轰炸活动,希望我得到一个合理的结果。

所以我的方法是计算每个细胞的轰炸效率指标,用最高值轰炸细胞,......重复这个过程,直到我把所有东西弄平。一些人主张使用简单的潜在损害(即从0到9的分数)作为度量标准,但是通过敲击高价值细胞并且不利用损伤重叠而不足。我计算cell value - sum of all neighbouring cells,将任何正数重置为0并使用任何负数的绝对值。直观地,这个指标应该做出一个选择,有助于最大限度地减少高计数细胞的损伤重叠,而不是直接敲击它们。

下面的代码在28枚炸弹中完全破坏了测试区域(请注意,使用潜在损害作为公制产生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();
    }           
  }
}

产生的轰炸模式输出如下(左侧的字段值,右侧的度量标准)

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

答案 22 :(得分:1)

您可以使用状态空间规划。 例如,使用A *(或其中一个变体)与启发式f = g + h结合使用,如下所示:

  • g:到目前为止投下的炸弹数量
  • h:对网格的所有值除以9的总和(这是最好的结果,这意味着我们有一个可接受的启发式算法)

答案 23 :(得分:1)

我也有28个动作。我使用了两个测试来获得最佳的下一步:首先是为董事会产生最小金额。第二,对于相等的总和,产生最大密度的移动定义为:

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

这是Haskell。 “解决板”显示了引擎的解决方案。您可以通过键入“main”来玩游戏,然后输入目标点,“最佳”用于推荐,或“退出”退出。

输出:
*主&GT;解决董事会 [(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)

答案 24 :(得分:1)

这是我的解决方案..我不会在代码中写出来,因为我没有时间,但我相信这应该每次产生最佳数量的移动 - 虽然我不确定它会有多高效找到炸弹点。

首先,正如@Luka Rahne在其中一条评论中所述,你炸弹的顺序并不重要 - 只有组合。

其次,正如许多其他人所说,从角落对角线进行1次轰炸是最佳的,因为它比角落更接近点。

这为我的算法版本生成了基础: 我们可以在第一次或最后一次轰炸'从角落里掉一次',这无关紧要(理论上) 我们首先轰炸那些因为它使后来的决定更容易(在实践中) 我们轰炸影响最多点的点,同时轰炸那些角落。

让我们将阻力点定义为董事会中的点数,其中最不可爆炸点 + 最大数量为0

不可爆炸的分数可以定义为我们当前正在查看的董事会当前范围中不存在的分数。

我还将定义4个边界来处理我们的范围: Top = 0,Left = 0,Bottom = k,right = j。 (值开始)

最后,我将最佳炸弹定义为炸弹,这些炸弹落在与阻力点相邻的点上并且接触(1)最高值的阻力点和(2)最大的可能的分数。

关于这种方法 - 很明显我们是在外面工作。我们将能够同时使用4个'轰炸机'。

第一个阻力点显然是我们的角落。 “出界”点不可爆炸(每个角落范围外有5个点)。所以我们首先从角落对角地轰炸一点。

算法:

  1. 找到4个最佳炸弹点。
  2. 如果一个炸弹点正在轰击一个接触2个边界(即一个角落)的阻力点,则炸弹直到该点为0.否则,每次炸弹直到接触最佳炸弹点的一个阻力点为0。 / LI>
  3. 每个绑定: if(sum(bound)== 0)advance bound
  4. 重复直到TOP = BOTTOM和LEFT = RIGHT

    我稍后会尝试编写实际代码

答案 25 :(得分:1)

蛮力!

我知道它效率不高,但即使您找到了更快的算法,也可以随时测试此结果以了解其准确性。

使用一些递归,如下所示:

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

}

你可以通过缓存提高效率,如果不同的方式导致相同的结果,你不应该重复相同的步骤。

详细说明:

如果轰炸单元1,3,5导致与轰炸单元5,3,1相同的结果,那么,对于这两种情况,你不应该再次重新执行所有后续步骤,只有1就足够了,你应该存储所有表状态并使用其结果。

表统计信息的哈希值可用于快速比较。

答案 26 :(得分:1)

  1. 永远不要轰炸边界(除非广场没有非边境邻居)
  2. 零角落。
  3. 零角落,对角线(一个唯一的非边境邻居)的角落一个角落的值
  4. 这将创造新的角落。转到2
  5. 编辑:没有注意到Kostek提出了几乎相同的方法,所以现在我做出更强烈的主张: 如果选择要清除的角始终位于最外层,那么它是最佳的。

    在OP的例子中:将2(作为1 + 1或2)放在除5之外的其他任何东西上都不会导致击中5上的任何方格。所以我们只需要在5上下降2(在左下角1下降6 ......)

    在此之后,只有一种方法可以清除(在左上角)原来是1(现在为0)的角落,也就是在B3上删除0(excel就像符号)。 等等。

    只有在清除整个A和E列以及1和7行后,才能开始更深层地清除一层。

    考虑清除只有那些故意清除,清除0值的角落没有任何成本,并简化了对它的思考。

    因为所有掉落的炸弹必须掉落,这会导致清除场地,这是最佳解决方案。


    睡了好觉后,我意识到这不是真的。 考虑

      ABCDE    
    1 01000
    2 10000
    3 00000
    4 00000
    

    我的做法会在B3和C2上放下炸弹,当放弃B2就足够了

答案 27 :(得分:1)

如果您想要绝对最佳的解决方案来清洁电路板,您将不得不使用经典的回溯,但如果矩阵非常大,那么找到最佳解决方案需要很长时间,如果您想要一个&#34;可能&#34 ;最佳解决方案你可以使用贪心算法,如果你需要帮助编写算法我可以帮助你

来想想这是最好的方式。在那里制作另一个矩阵,通过在那里放置炸弹来存储你移除的点,然后选择具有最大点的单元并放下炸弹,更新点矩阵并继续。例如:

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))

每个相邻单元格的单元格值+1,其值大于0

答案 28 :(得分:0)

所有这些问题归结为计算编辑距离。简单地计算给定矩阵和零矩阵之间的Levenshtein距离的变体,其中编辑被爆炸替换,使用动态编程来存储中间阵列之间的距离。我建议使用矩阵的哈希作为关键。在伪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

答案 29 :(得分:0)

这是第一个问题的答案。我没有注意到他改变了参数。

创建所有目标的列表。根据drop(本身和所有邻居)影响的正值数量为目标分配值。最高价值是九。

按受影响的目标数量(降序)对目标进行排序,并对每个受影响目标的总和进行二级降序排序。

将炸弹放在排名最高的目标上,然后重新计算目标并重复,直到所有目标值都为零。

同意,这不是总是最优化的。例如,

100011
011100
011100
011100
000000
100011

这种方法需要5个炸弹才能清除。但最好的是,你可以在4中完成它 接近,没有回溯。在大多数情况下,它将是最佳的,或非常接近。

使用原始问题编号,这种方法解决了28个炸弹。

添加代码以演示此方法(使用带按钮的表单):

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

                    }
                }
            }
        }

    }

您需要的课程:

        class Target
    {
        public int value;
        public int sum;
        public int x;
        public int y;
    }

答案 30 :(得分:0)

到目前为止,有几个答案给出了指数时间,其中一些涉及动态编程。我怀疑这些是否必要。

我的解决方案是 O(mnS),其中 m,n 是电路板的尺寸, S 是所有整数的总和。这个想法是相当强大的:找到每次都可以杀死最多并且终止于0的位置。

它为给出的板提供了28次移动,并在每次下降后打印出板。

完整,不言自明的代码:

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

}