我有一个nxm个由非负整数组成的矩阵。例如:
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:我相信当我们有几个相同的情况需要最少的炸弹来清除上面的线时,我们会选择在“左侧”使用最多炸弹的一个。还有什么证据是正确的吗?
这将是一个贪婪的方法:
计算一个阶为n X m的“score”矩阵,其中score[i][j]是如果位置(i,j)被炸毁,则矩阵中各点的总扣除额。(一个点的最高分数是9分,最低分数是0分)
逐行移动,找到并选择第一个得分最高的位置(例如(i,j))。
炸弹(i, j)。增加炸弹数量。
如果原矩阵的所有元素都不为零,则转到1。
但我怀疑这是否是最佳解决方案。
编辑:
我上面提到的贪心方法,虽然有效,但很可能不能给我们最优的解决方案。所以我想应该添加一些DP的元素。
我想我们可以同意,在任何时候,具有最高“分数”(分数[I][j] =总扣分,如果(I,j)被炸)的位置之一必须被瞄准。从这个假设开始,下面是新的方法:
NumOfBombs(M):(返回所需的最小炸弹数量)
给定一个矩阵M (n X M),如果M中的所有元素都为0,则返回0。
计算“分数”矩阵M。
设k个不同的位置P1 P2…Pk (1 <= k <= n*m),为m中得分最高的位置。
return (1 + min(NumOfBombs(M1), NumOfBombs(M2),…, NumOfBombs(Mk))
M1, M2,……,Mk是我们轰炸位置P1, P2,…, Pk。
此外,如果我们想在此基础上破坏位置的顺序,我们必须跟踪“min”的结果。
蛮力!
我知道它效率不高,但即使你找到了一个更快的算法,你也可以对这个结果进行测试,以了解它有多准确。
使用一些递归,像这样:
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就足够了,您应该将所有表状态存储在某个地方并使用其结果。
表统计信息的散列可以用于快速比较。
对于更新后的问题,简单的贪心算法可以得到最优结果。
向单元格A[1,1]投掷A[0,0]炸弹,然后向单元格A[2,1]投掷A[1,0]炸弹,并继续向下此过程。要清除左下角,向单元格A[n -2,1]投掷max(A[n -1,0], A[n -2,0], A[n -3,0])炸弹。这将完全清除前3列。
用同样的方法清除第3、4、5列,然后是第6、7、8列,等等。
不幸的是,这并不能帮助找到最初问题的解决方案。
“更大”的问题(没有“非增加”约束)可能被证明是np困难的。这是证明的草图。
假设我们有一个度为3的平面图形。我们来求这个图的最小顶点覆盖。根据维基百科的文章,这个问题对于3次以下的平面图形是np困难的。这可以通过平面3SAT的简化来证明。平面3SAT的硬度由3SAT降低而成。这两个证明都在Erik Demaine教授最近的“算法下界”讲座(第7和第9讲)中提出。
如果我们分割原始图的一些边(图中左边的图),每条边都有偶数个额外的节点,结果图(图中右边的图)应该对原始顶点具有完全相同的最小顶点覆盖。这样的转换允许将图顶点对齐到网格上的任意位置。
如果我们将图顶点只放置在偶数行和列上(这样就不会有两条边与一个顶点形成锐角),在有边的地方插入“1”,在其他网格位置插入“0”,我们可以使用原始问题的任何解决方案来找到最小顶点覆盖。
这将是一个贪婪的方法:
计算一个阶为n X m的“score”矩阵,其中score[i][j]是如果位置(i,j)被炸毁,则矩阵中各点的总扣除额。(一个点的最高分数是9分,最低分数是0分)
逐行移动,找到并选择第一个得分最高的位置(例如(i,j))。
炸弹(i, j)。增加炸弹数量。
如果原矩阵的所有元素都不为零,则转到1。
但我怀疑这是否是最佳解决方案。
编辑:
我上面提到的贪心方法,虽然有效,但很可能不能给我们最优的解决方案。所以我想应该添加一些DP的元素。
我想我们可以同意,在任何时候,具有最高“分数”(分数[I][j] =总扣分,如果(I,j)被炸)的位置之一必须被瞄准。从这个假设开始,下面是新的方法:
NumOfBombs(M):(返回所需的最小炸弹数量)
给定一个矩阵M (n X M),如果M中的所有元素都为0,则返回0。
计算“分数”矩阵M。
设k个不同的位置P1 P2…Pk (1 <= k <= n*m),为m中得分最高的位置。
return (1 + min(NumOfBombs(M1), NumOfBombs(M2),…, NumOfBombs(Mk))
M1, M2,……,Mk是我们轰炸位置P1, P2,…, Pk。
此外,如果我们想在此基础上破坏位置的顺序,我们必须跟踪“min”的结果。
这可以用深度为O(3^(n))的树来求解。其中n是所有平方和。
首先考虑用O(9^n)树来解决问题是很简单的,只需考虑所有可能的爆炸位置。有关示例,请参阅Alfe的实现。
接下来我们意识到,我们可以从下往上轰炸,仍然得到一个最小的轰炸模式。
Start from the bottom left corner.
Bomb it to oblivion with the only plays that make sense (up and to the right).
Move one square to the right.
While the target has a value greater than zero, consider each of the 2 plays that make sense (straight up or up and to the right), reduce the value of the target by one, and make a new branch for each possibility.
Move another to the right.
While the target has a value greater than zero, consider each of the 3 plays that make sense (up left, up, and up right), reduce the value of the target by one, and make a new branch for each possibility.
Repeat steps 5 and 6 until the row is eliminated.
Move up a row and repeat steps 1 to 7 until the puzzle is solved.
这个算法是正确的,因为
有必要在某一时刻完成每一行。
完成一行总是需要一个游戏,一个在上面,一个在下面,或者在这一行内。
选择在未清除的最低行之上的玩法总是比选择在该行之上或该行之下的玩法更好。
在实践中,这个算法通常会比它的理论最大值做得更好,因为它会定期轰炸邻居并减少搜索的大小。如果我们假设每次轰炸都会减少4个额外目标的价值,那么我们的算法将运行在O(3^(n/4))或大约O(1.3^n)。
Because this algorithm is still exponential, it would be wise to limit the depth of the search. We might limit the number of branches allowed to some number, X, and once we are this deep we force the algorithm to choose the best path it has identified so far (the one that has the minimum total board sum in one of its terminal leaves). Then our algorithm is guaranteed to run in O(3^X) time, but it is not guaranteed to get the correct answer. However, we can always increase X and test empirically if the trade off between increased computation and better answers is worthwhile.
Well, suppose we number the board positions 1, 2, ..., n x m. Any sequence of bomb drops can be represented by a sequence of numbers in this set, where numbers can repeat. However, the effect on the board is the same regardless of what order you drop the bombs in, so really any choice of bomb drops can be represented as a list of n x m numbers, where the first number represents the number of bombs dropped on position 1, the second number represents the number of bombs dropped on position 2, etc. Let's call this list of n x m numbers the "key".
你可以试着先计算1个炸弹投下的所有板子状态,然后用这些来计算2个炸弹投下的所有板子状态,等等,直到你得到所有的0。但是在每一步中,您都将使用上面定义的键缓存状态,因此您可以在计算下一步时使用这些结果(一种“动态规划”方法)。
但是根据n、m的大小和网格中的数字,这种方法的内存需求可能会过多。一旦你计算了N + 1的所有结果,你就可以抛弃N个炸弹投掷的所有结果,所以这里有一些节省。当然,您不能以花费更长的时间为代价缓存任何东西——动态编程方法以内存换取速度。