我有一个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:我相信当我们有几个相同的情况需要最少的炸弹来清除上面的线时,我们会选择在“左侧”使用最多炸弹的一个。还有什么证据是正确的吗?


当前回答

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个炸弹投掷的所有结果,所以这里有一些节省。当然,您不能以花费更长的时间为代价缓存任何东西——动态编程方法以内存换取速度。

其他回答

这将是一个贪婪的方法:

计算一个阶为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”的结果。

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个炸弹投掷的所有结果,所以这里有一些节省。当然,您不能以花费更长的时间为代价缓存任何东西——动态编程方法以内存换取速度。

没有必要将问题转化为线性子问题。

相反,使用简单的贪婪启发式,即从最大的角落开始轰炸角落。

在给定的例子中,有四个角,{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次爆炸消除3 0 2。现在,我们有两个新的角,在第四行{2,1}。我们炸了那些,再炸3次。我们现在已经将矩阵化简为4 * 4。有一个角落,左上角。我们搞砸了。现在我们还有两个角,{5,3}。因为5是最大的角,我们首先轰炸5个角,然后最后轰炸另一个角的3。总数是13+3+3+1+5+3 = 28。

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

创建一个所有目标的列表。根据掉落物品(掉落物品本身和所有邻居)影响的正数值的数量为目标分配一个值。最高值是9。

根据受影响目标的数量(降序)对目标进行排序,对每个受影响目标的和进行二次降序排序。

向排名最高的目标投掷炸弹,然后重新计算目标,直到所有目标值都为零。

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

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

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

正如评论中指出的那样,它在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任务。(因为你需要使它为空)。下降。别忘了最后一排。