我有一个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:我相信当我们有几个相同的情况需要最少的炸弹来清除上面的线时,我们会选择在“左侧”使用最多炸弹的一个。还有什么证据是正确的吗?
在这里,线性规划方法似乎非常有用。
设Pm x n为包含位置值的矩阵:
现在定义一个炸弹矩阵B(x, y)m x n,其中1≤x≤m, 1≤y≤n如下所示
以这样一种方式
例如:
所以我们正在寻找一个矩阵Bm x n = [bij]
可以定义为炸弹矩阵的和:
(qij将是我们在pij位置投放的炸弹数量)
pij - bij≤0(为了更简洁,我们称之为P - B≤0)
而且,B应该使和最小。
我们也可以把B写成前面的丑矩阵:
由于P - B≤0(即P≤B),我们得到了如下线性不等式系统:
qmn x1定义为
PMN x 1定义为
我们可以说我们有一个方程组是smnxmn这个矩阵要倒转来解方程组。我自己没有扩展它,但我相信在代码中应该很容易做到。
现在,我们有一个最小的问题可以表述为
I believe it is something easy, almost trivial to be solved with something like the simplex algorithm (there is this rather cool doc about it). However, I do know almost no linear programming (I will take a course about it on Coursera but it is just in the future...), I had some headaches trying to understand it and I have a huge freelance job to finish so I just give up here. It can be that I did something wrong at some point, or that it can't go any further, but I believe this path can eventually lead to the solution. Anyway, I am anxious for your feedback.
(特别感谢这个神奇的网站从LaTeX表达式创建图片)
这个贪婪的解决方案似乎是正确的:
正如评论中指出的那样,它在2D中会失败。但也许你可以改进它。
1 d:
如果至少有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任务。(因为你需要使它为空)。下降。别忘了最后一排。
这可以用深度为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.
这是我的解决方案。由于时间有限,我不会用代码写出来,但我相信这应该每次都能产生最优的移动数量——尽管我不确定它在寻找要轰炸的点时是否有效。
首先,正如@Luka Rahne在一条评论中所说的,你轰炸的顺序并不重要,重要的是组合。
其次,正如许多人所说的那样,从角的对角线上轰炸1是最优的,因为它接触的点比角多。
这就生成了我的算法版本的基础:
我们可以在第一个或最后一个炸掉拐角的1-off,这没有关系(理论上)
我们首先破坏这些,因为它可以让后面的决定更容易(在实践中)
我们轰炸影响最大的点,同时轰炸那些角落。
让我们将阻力点定义为棋盘上具有最多不可炸点+周围0数量最多的点
非爆炸点可以定义为在我们正在研究的黑板的当前范围内不存在的点。
我还将定义4个处理范围的边界:
上=0,左=0,下=k,右=j。
(起始值)
最后,我将最优炸弹定义为投掷在与阻力点相邻的点上的炸弹,并接触(1)最高值的阻力点和(2)可能的最大数量的点。
关于方法,很明显我们正在从外到内的工作。我们将能够同时与4架“轰炸机”一起工作。
第一个阻力点显然是我们的弯道。“边界外”的点是不可轰炸的(每个角落的范围外都有5个点)。所以我们先在对角线上炸一个角。
算法:
找到4个最佳炸弹点。
如果一个炸弹点正在轰炸一个接触2个边界(即一个角)的阻力点,则一直轰炸到该点为0。否则,逐个轰炸,直到其中一个触及最佳轰炸点的阻力点为0。
对于每个边界:
如果(sum(bound)==0)前进界
重复以上步骤,直到上=下,左=右
稍后我将尝试编写实际代码
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个炸弹投掷的所有结果,所以这里有一些节省。当然,您不能以花费更长的时间为代价缓存任何东西——动态编程方法以内存换取速度。
这是另一个想法:
让我们先给黑板上的每个空格分配一个权重,计算在那里扔炸弹会减少多少数字。如果这个空间有一个非零数,它就得到一个点,如果它的相邻空间有一个非零数,它就得到一个额外的点。如果这是一个1000 * 1000的网格,我们为这100万个空间中的每一个都分配了权重。
然后根据权重对列表中的空格进行排序,并轰炸权重最高的空格。可以这么说,这是我们最大的收获。
在此之后,更新每个空间的重量是受炸弹的影响。这是你轰炸的空间,和它相邻的空间,以及它们相邻的空间。换句话说,任何空间的价值都可能因为爆炸而减少为零,或者相邻空间的价值减少为零。
然后,根据权重重新排序列表空间。由于轰炸只改变了一小部分空间的权重,因此不需要使用整个列表,只需在列表中移动这些空间。
轰炸新的最高权重空间,并重复上述步骤。
这保证了每次轰炸都能减少尽可能多的空格(基本上,它会击中尽可能少的已经为零的空格),所以这是最优的,除非它们的权重是相同的。所以你可能需要做一些回溯跟踪,当有一个平局的顶部重量。不过,只有最高重量的领带重要,其他领带不重要,所以希望没有太多的回溯。
Edit:
Mysticial's counterexample below demonstrates that in fact this isn't guaranteed to be optimal, regardless of ties in weights. In some cases reducing the weight as much as possible in a given step actually leaves the remaining bombs too spread out to achieve as high a cummulative reduction after the second step as you could have with a slightly less greedy choice in the first step. I was somewhat mislead by the notion that the results are insensitive to the order of bombings. They are insensitive to the order in that you could take any series of bombings and replay them from the start in a different order and end up with the same resulting board. But it doesn't follow from that that you can consider each bombing independently. Or, at least, each bombing must be considered in a way that takes into account how well it sets up the board for subsequent bombings.