Bomb dropping algorithm

后端 未结 30 827
挽巷
挽巷 2021-01-29 16:55

I have an n x m matrix consisting of non-negative integers. For example:

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


        
相关标签:
30条回答
  • 2021-01-29 17:46

    You can represent this problem as integer programming problem. (this is just one of possible solutions to approach this problem)

    Having points:

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

    one can write 16 equations where for point f for example holds

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

    minimaised over sum of all indexes and integer solution.

    Solution is of course sum of this indexes.

    This can be further simplified by setting all xi on boundaries 0, so you end up having 4+1 equation in this example.

    Problem is that there is no trivial algorhitm for solving such problems. tI am not expert on this, but solving this problem as linear programming is NP hard.

    0 讨论(0)
  • 2021-01-29 17:46

    This is a partial answer, I'm trying to find a lower bound and upper bound that could be the possible number of bombs.

    In 3x3 and smaller board, the solution is trivially always the largest numbered cell.

    In boards larger than 4x4, the first obvious lower bound is the sum of the corners:

    *2* 3  7 *1*
     1  5  6  2
     2  1  3  2
    *6* 9  6 *4*
    

    however you arrange the bomb, it is impossible to clear this 4x4 board with less than 2+1+6+4=13 bombs.

    It has been mentioned in other answers that placing the bomb on the second-to-corner to eliminate the corner is never worse than placing the bomb on the corner itself, so given the 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*
    

    We can zero the corners out by placing bombs on the second-to-corner to give a new board:

     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
    

    So far so good. We need 13 bombs to clear the corners.

    Now observe the number 6, 4, 3, and 2 marked below:

     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
    

    There is no way to bomb any two of those cells using a single bomb, so the minimum bomb has increased by 6+4+3+2, so adding to the number of bombs we used to clear the corners, we get that the minimum number of bombs required for this map has become 28 bombs. It is impossible to clear this map with less than 28 bombs, this is the lower bound for this map.

    You can use greedy algorithm to establish an upper bound. Other answers have shown that a greedy algorithm produces a solution that uses 28 bombs. Since we've proven earlier that no optimal solution can have less than 28 bombs, therefore 28 bombs is indeed an optimal solution.

    When greedy and the method to find the minimal bound I've mentioned above does not converge though, I guess you do have to go back to checking all combinations.

    The algorithm for finding the lower bound is the following:

    1. Pick an element with the highest number, name it P.
    2. Mark all cells two steps away from P and P itself as unpickable.
    3. Add P to the minimums list.
    4. Repeat to step 1 until all cells are unpickable.
    5. Sum the minimums list to get the lower bound.
    0 讨论(0)
  • 2021-01-29 17:46

    evaluation function, total sum:

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

    objective function:

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

    destroy function:

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

    goal function:

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

    linear maximization function:

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

    This is not optimal, but can be optimized through finding a better evaluation function ..

    .. but thinking about this problem, I was thinking that one of the main issues is getting abandoned figures in the middle of zeroes at some point, so I'd take another approach .. which is dominate minimal values into zero, then try to escape zeroes as possible, which lead to general minimization of minimal existing value(s) or so

    0 讨论(0)
  • 2021-01-29 17:49

    It seems that a linear programming approach can be very helpful here.

    Let Pm x n be the matrix with the values of the positions:

    Matrix of positions

    Now let define a bomb matrix B(x, y)m x n,with 1 ≤ x ≤ m, 1 ≤ y ≤ n as below

    Bomb matrix

    in such a way that

    Values of positions in bomb matrix

    For example:

    B(3, 3)

    So we are looking to a matrix Bm x n = [bij] that

    1. Can be defined as a sum of bomb matrices:

      B as a sum of bomb matrices

      (qij would be then the quantity of bombs we would drop in position pij)

    2. pij - bij ≤ 0 (to be more succint, let us say it as P - B ≤ 0)

    Also, B should minimize the sum sum of quantities of bombs.

    We can also write B as the ugly matrix ahead:

    B as a matrix of sum of quantities

    and since P - B ≤ 0 (which means P ≤ B) we have the following pretty linear inequality system below:

    Relationship between number of bombs dropped and values in positions

    Being qmn x 1 defined as

    Vector of quantities

    pmn x 1 defined as

    Values of P distributed as a vector

    We can say we have a system The system below represented as product of matrices http://latex.codecogs.com/gif.download?S%5Cmathbf%7Bq%7D&space;%5Cge&space;%5Cmathbf%7Bp%7D being Smn x mn the matrix to be reversed to solve the system. I did not expand it myself but I believe it should be easy to do it in code.

    Now, we have a minimum problem which can be stated as

    The system we have to solve

    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.

    (Special thanks for this amazing site to create pictures from LaTeX expressions)

    0 讨论(0)
  • 2021-01-29 17:49

    I believe that to minimize the amount of bombs you simply need maximize the amount of damage.. for that to happen need to check the area that has the strongest force.. so you first analyze the field with a 3x3 kernel and check where the sum is stronger.. and bomb there.. and do until the field is flat.. for this filed the answer is 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');
    
    0 讨论(0)
  • 2021-01-29 17:50

    There is a way to reduce this to a simple sub-problem.

    There are 2 parts to the explanation, the algorithm, and the reason the algorithm provides an optimal solution. The first won't make sense without the second, so I'll start with the why.

    If you think of bombing the rectangle (assume a big rectangle - no edge cases yet) you can see that the only way to reduce the hollow rectangle of squares on the perimeter to 0 is to bomb either the perimeter or to bomb the hollow rectangle of squares just inside the perimeter. I'll call the perimeter layer 1, and the rectangle inside it layer 2.

    An important insight is that there is no point bombing layer 1, because the "blast radius" you get from doing so is always contained within the blast radius of another square from layer 2. You should be able to easily convince yourself of this.

    So, we can reduce the problem to finding an optimal way to bomb away the perimeter, then we can repeat that until all squares are 0.

    But of course, that won't always find an optimal solution if it's possible to bomb away the perimeter in a less than optimal fashion, but by using X extra bombs make the problem of reducing the inner layer simpler by >X bombs. So, if we call the permiter layer one, if we place an extra X bombs somewhere in layer 2 (just inside layer 1), can we reduce the effort of later bombing away layer 2 by more than X? In other words, we have to prove we can be greedy in reducing the outer perimeter.

    But, we do know we can be greedy. Because no bomb in layer 2 can ever be more efficient in reducing layer 2 to 0 than a strategically placed bomb in layer 3. And for the same reason as before - there is always a bomb we can place in layer 3 that will affect every square of layer 2 that a bomb placed in layer 2 can. So, it can never harm us to be greedy (in this sense of greedy).

    So, all we have to do is find the optimal way to reduce the permiter to 0 by bombing the next inner layer.

    We are never hurt by first bombing the corner to 0, because only the corner of the inner layer can reach it, so we really have no choice (and, any bomb on the perimeter that can reach the corner has a blast radius contained in the blast radius from the corner of the inner layer).

    Once we have done so, the squares on the perimeter adjacent to the 0 corner can only be reached by 2 squares from the inner layer:

    0       A       B
    
    C       X       Y
    
    D       Z
    

    At this point the perimeter is effectively a closed 1 dimensional loop, because any bomb will reduce 3 adjacent squares. Except for some weirdness near the corners - X can "hit" A,B,C,and D.

    Now we can't use any blast radius tricks - the situation of each square is symmetric, except for the weird corners, and even there no blast radius is a subset of another. Note that if this were a line (as Colonel Panic discusses) instead of a closed loop the solution is trivial. The end points must be reduced to 0, and it never harms you to bomb the points adjacent to the end points, again because the blast radius is a superset. Once you have made your endpoint 0, you still have a new endpoint, so repeat (until the line is all 0).

    So, if we can optimally reduce a single square in the layer to 0 we have an algorithm (because we have cut the loop and now have a straight line with endpoints). I believe bombing adjacent to the square with the lowest value (giving you 2 options) such that the highest value within 2 squares of that lowest value is the minimum possible (you may have to split your bombing to manage this) will be optimal but I don't (yet?) have a proof.

    0 讨论(0)
提交回复
热议问题