Bomb dropping algorithm

后端 未结 30 828
挽巷
挽巷 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:36

    This can be solved using a tree of depth O(3^(n)). Where n is the sum of all of the squares.

    First consider that it is trivial to solve the problem with a tree of O(9^n), simply consider all of the possible bombing locations. For an example see Alfe's implementation.

    Next realize that we can work to bomb from the bottom up and still get a minimum bombing pattern.

    1. Start from the bottom left corner.
    2. Bomb it to oblivion with the only plays that make sense (up and to the right).
    3. Move one square to the right.
    4. 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.
    5. Move another to the right.
    6. 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.
    7. Repeat steps 5 and 6 until the row is eliminated.
    8. Move up a row and repeat steps 1 to 7 until the puzzle is solved.

    This algorithm is correct because

    1. It is necessary to complete each row at some point.
    2. Completing a row always requires a play either one above, one below, or within that row.
    3. It is always as good or better to choose a play one above the lowest uncleared row than a play on the row or below the row.

    In practice this algorithm will regularly do better than its theoretical maximum because it will regularly bomb out neighbors and reduce the size of the search. If we assume that each bombing decreases the value of 4 additional targets, then our algorithm will run in O(3^(n/4)) or approximately 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.

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

    Mathematica Integer Linear Programming using branch-and-bound

    As it has already been mentioned, this problem can be solved using integer linear programming (which is NP-Hard). Mathematica already has ILP built in. "To solve an integer linear programming problem Mathematica first solves the equational constraints, reducing the problem to one containing inequality constraints only. Then it uses lattice reduction techniques to put the inequality system in a simpler form. Finally, it solves the simplified optimization problem using a branch-and-bound method." [see Constrained Optimization Tutorial in Mathematica.. ]

    I've written the following code that utilizes ILP libraries of Mathematica. It is surprisingly fast.

    solveMatrixBombProblem[problem_, r_, c_] := 
     Module[{}, 
      bombEffect[x_, y_, m_, n_] := 
       Table[If[(i == x || i == x - 1 || i == x + 1) && (j == y || 
            j == y - 1 || j == y + 1), 1, 0], {i, 1, m}, {j, 1, n}];
      bombMatrix[m_, n_] := 
       Transpose[
        Table[Table[
          Part[bombEffect[(i - Mod[i, n])/n + 1, Mod[i, n] + 1, m, 
            n], (j - Mod[j, n])/n + 1, Mod[j, n] + 1], {j, 0, 
           m*n - 1}], {i, 0, m*n - 1}]];
      X := x /@ Range[c*r];
      sol = Minimize[{Total[X], 
         And @@ Thread[bombMatrix[r, c].X >= problem] && 
          And @@ Thread[X >= 0] && Total[X] <= 10^100 && 
          Element[X, Integers]}, X];
      Print["Minimum required bombs = ", sol[[1]]];
      Print["A possible solution = ", 
       MatrixForm[
        Table[x[c*i + j + 1] /. sol[[2]], {i, 0, r - 1}, {j, 0, 
          c - 1}]]];]
    

    For the example provided in the problem:

    solveMatrixBombProblem[{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}, 7, 5]
    

    Outputs

    enter image description here

    For anyone reading this with a greedy algorithm

    Try your code on the following 10x10 problem:

    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
    

    Here it is comma-seperated:

    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
    

    For this problem, my solution contains 208 bombs. Here's a possible solution (I was able to solve this in about 12 seconds).

    enter image description here

    As a way to test the results Mathematica is producing, see if your greedy algorithm can do any better.

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

    There seems to be a nonbipartite matching substructure here. Consider the following instance:

    0010000
    1000100
    0000001
    1000000
    0000001
    1000100
    0010000
    

    The optimal solution to this case has size 5 since that's the size of a minimum cover of the vertices of a 9-cycle by its edges.

    This case, in particular, shows that the linear programming relaxation a few people have posted isn't exact, doesn't work, and all those other bad things. I'm pretty sure I can reduce "cover the vertices of my planar cubic graph by as few edges as possible" to your problem, which makes me doubt whether any of the greedy/hill-climbing solutions are going to work.

    I don't see a way to solve this in polynomial time in the worst case. There might be a very clever binary-search-and-DP solution that I'm not seeing.

    EDIT: I see that the contest (http://deadline24.pl) is language-agnostic; they send you a bunch of input files and you send them outputs. So you don't need something that runs in worst-case polynomial time. In particular, you get to look at the input!

    There are a bunch of small cases in the input. Then there's a 10x1000 case, a 100x100 case, and a 1000x1000 case. The three large cases are all very well-behaved. Horizontally adjacent entries typically have the same value. On a relatively beefy machine, I'm able to solve all of the cases by brute-forcing using CPLEX in just a couple of minutes. I got lucky on the 1000x1000; the LP relaxation happens to have an integral optimal solution. My solutions agree with the .ans files provided in the test data bundle.

    I'd bet you can use the structure of the input in a much more direct way than I did if you took a look at it; seems like you can just pare off the first row, or two, or three repeatedly until you've got nothing left. (Looks like, in the 1000x1000, all of the rows are nonincreasing? I guess that's where your "part B" comes from? )

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

    This greedy solution seems to be correct:

    As pointed in comments, it'll fail in 2D. But maybe you may improve it.

    For 1D:
    If there is at least 2 numbers you don't need to shoot to the leftmost one because shooting to the second is not worse. So shoot to the second, while first isn't 0, because you have to do it. Move to the next cell. Don't forget about last cell.

    C++ code:

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

    So for 2D:
    Again: you don't need to shoot in the first row (if there is the second). So shoot to the second one. Solve 1D task for first row. (because you need to make it null). Go down. Don't forget last row.

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

    If you want the absolute optimal solution to clean the board you will have to use classic backtracking, but if the matrix is very big it will take ages to find the best solution, if you want an "possible" optimal solution you can use greedy algorithm, if you need help writing the algorithm i can help you

    Come to think of it that is the best way. Make another matrix there you store the points you remove by dropping a bomb there then chose the cell with maximum points and drop the bomb there update the points matrix and continue. Example:

    2 3 5 -> (2+(1*3)) (3+(1*5)) (5+(1*3))
    1 3 2 -> (1+(1*4)) (3+(1*7)) (2+(1*4))
    1 0 2 -> (1+(1*2)) (0+(1*5)) (2+(1*2))
    

    cell value +1 for every adjacent cell with a value higher than 0

    0 讨论(0)
  • 2021-01-29 17:39
    1. Never bomb border (unless square does not have nonborder neighbour)
    2. Zero corner.
    3. To zero corner, drop value of corner one square away diagonaly (the only nonborder neighbour)
    4. This will create new corners. Go to 2

    Edit: did not notice that Kostek suggested almost same approach, so now I make stronger claim: If corners to clear are chosen to be always on outermost layer, then it is optimal.

    In OP's example: dropping 2 (as 1+1 or 2) on anything else than on 5 does not leads to hitting any square that dropping on 5 would hit. So we simply must drop 2 on 5 (and 6 on lower left 1 ...)

    After this, there is only one way how to clear (in top left) corner what was originaly 1 (now 0), and that is by dropping 0 on B3 (excel like notation). And so on.

    Only after clearing whole A and E columns and 1 and 7 rows, start clearing one layer deeper.

    Consider cleared only those intentionaly cleared, clearing 0 value corners costs nothing and simplifies thinking about it.

    Because all bombs dropped this way must be dropped and this leads to cleared fields, it is optimal solution.


    After good sleep I realized that this is not true. Consider

      ABCDE    
    1 01000
    2 10000
    3 00000
    4 00000
    

    My approach would drop bombs on B3 and C2, when dropping on B2 would be enough

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