我有一个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:我相信当我们有几个相同的情况需要最少的炸弹来清除上面的线时,我们会选择在“左侧”使用最多炸弹的一个。还有什么证据是正确的吗?
这是另一个想法:
让我们先给黑板上的每个空格分配一个权重,计算在那里扔炸弹会减少多少数字。如果这个空间有一个非零数,它就得到一个点,如果它的相邻空间有一个非零数,它就得到一个额外的点。如果这是一个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.
这是部分答案,我试图找到一个下界和上界,可能是炸弹的数量。
在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个炸弹确实是一个最优解。
当贪心和我上面提到的寻找最小界的方法不收敛时,我猜你必须回去检查所有的组合。
求下界的算法如下:
选一个数值最大的元素,命名为P。
将所有距离P和P本身两步远的单元格标记为不可拾取。
将P添加到最小值列表中。
重复步骤1,直到所有单元格都不可拾取。
对最小值列表求和得到下界。
这个贪婪的解决方案似乎是正确的:
正如评论中指出的那样,它在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任务。(因为你需要使它为空)。下降。别忘了最后一排。
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个炸弹投掷的所有结果,所以这里有一些节省。当然,您不能以花费更长的时间为代价缓存任何东西——动态编程方法以内存换取速度。