炸弹投放算法

Posted

技术标签:

【中文标题】炸弹投放算法【英文标题】:Bomb dropping algorithm 【发布时间】:2013-02-24 08:39:28 【问题描述】:

我有一个由非负整数组成的n x m 矩阵。例如:

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

【问题讨论】:

好吧,我只是发现有些字段可以像示例中那样跳过 2 3 1 5 将其放在 2,3,1 上是没有意义的,因为放在它们上会导致一些子集损坏,我们可以通过放弃 5. 但找不到如何让它在全球范围内工作(如果它是正确的方式)。清除 2 需要使用 2 颗投在任何邻居身上的炸弹,而 5 则包含其他组的伤害。但是我不知道以后要做什么,因为当您重写它时(减少后),那么您有两个选择(没有一个超级组损坏)。 这是 NP 难的吗?它看起来是Maximum Coverage Problem 的变体。 这看起来有点像扫雷,除了你可以在一个点上多次放置炸弹,数字只表示在一个点上和周围的最小炸弹数量,而不是确切的数量。跨度> 也许你应该澄清一下,你说的问题是:what's the minimum amount of bombs required to clean the board?这是否意味着不一定需要找到实际的轰炸模式,而只需要找到最少数量的炸弹? @us2012:找到可能的炸弹数量的下限和上限相当容易,如果它们匹配,则必须是所需的确切炸弹数量,无需计算即可找到实际的模式。但这种情况可能只会发生一次,如果有的话。 【参考方案1】:

最慢但最简单且无错误的算法是生成并测试所有有效的可能性。对于这种情况非常简单(因为结果与炸弹放置的顺序无关)。

    创建N次应用bomp的函数 为所有 bompb-placement/bomb-count 可能性创建循环(当 matrix==0 时停止) 永远记住最好的解决方案。 在循环结束时,您有最佳解决方案 不仅是炸弹的数量,还有它们的位置

代码可能如下所示:

void copy(int **A,int **B,int m,int n)
    
    for (int i=0;i<m;i++)
     for (int j=0;i<n;j++)
       A[i][j]=B[i][j];
    

bool is_zero(int **M,int m,int n)
    
    for (int i=0;i<m;i++)
     for (int j=0;i<n;j++)
      if (M[i][j]) return 0;
    return 1;
    

void drop_bomb(int **M,int m,int n,int i,int j,int N)
    
    int ii,jj;
    ii=i-1; jj=j-1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj]))  M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; 
    ii=i-1; jj=j  ; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj]))  M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; 
    ii=i-1; jj=j+1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj]))  M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; 
    ii=i  ; jj=j-1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj]))  M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; 
    ii=i  ; jj=j  ; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj]))  M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; 
    ii=i  ; jj=j+1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj]))  M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; 
    ii=i+1; jj=j-1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj]))  M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; 
    ii=i+1; jj=j  ; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj]))  M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; 
    ii=i+1; jj=j+1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj]))  M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; 
    

void solve_problem(int **M,int m,int n)
    
    int i,j,k,max=0;
    // you probably will need to allocate matrices P,TP,TM yourself instead of this:
    int P[m][n],min;             // solution: placement,min bomb count
    int TM[m][n],TP[m][n],cnt;   // temp
    for (i=0;i<m;i++)            // max count of bomb necessary to test
     for (j=0;j<n;j++)
      if (max<M[i][j]) max=M[i][j];
    for (i=0;i<m;i++)            // reset solution
     for (j=0;j<n;j++)
      P[i][j]=max;
    min=m*n*max; 
        copy(TP,P,m,n); cnt=min;

    for (;;)  // generate all possibilities
        
        copy(TM,M,m,n);
        for (i=0;i<m;i++)   // test solution
         for (j=0;j<n;j++)
          drop_bomb(TM,m,n,TP[i][j]);
        if (is_zero(TM,m,n))// is solution
         if (min>cnt)       // is better solution -> store it
            
            copy(P,TP,m,n); 
            min=cnt;    
            
        // go to next possibility
        for (i=0,j=0;;)
            
            TP[i][j]--;
            if (TP[i][j]>=0) break;
            TP[i][j]=max;
                 i++; if (i<m) break;
            i=0; j++; if (j<n) break;
            break;
            
        if (is_zero(TP,m,n)) break;
        
    //result is in P,min
    

这可以通过多种方式进行优化,...最简单的方法是使用 M 矩阵重置解决方案,但您需要更改最大值以及 TP[][] 减量代码

【讨论】:

【参考方案2】:

到目前为止,有几个答案给出了指数时间,其中一些涉及动态编程。我怀疑这些是否有必要。

我的解决方案是O(mnS),其中m, n 是棋盘的尺寸,S 是所有整数的总和。这个想法相当蛮力:找到每次杀死最多的位置并在0处终止。

它为给定的棋盘提供 28 步棋,并且在每次掉落后打印出棋盘。

完整、不言自明的代码:

import java.util.Arrays;

public class BombMinDrops 

    private static final int[][] 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;
    private static final int ROWS = BOARD.length;
    private static final int COLS = BOARD[0].length;
    private static int remaining = 0;
    private static int dropCount = 0;
    static 
        for (int i = 0; i < ROWS; i++) 
            for (int j = 0; j < COLS; j++) 
                remaining = remaining + BOARD[i][j];
            
        
    

    private static class Point 
        int x, y;
        int kills;

        Point(int x, int y, int kills) 
            this.x = x;
            this.y = y;
            this.kills = kills;
        

        @Override
        public String toString() 
            return dropCount + "th drop at [" + x + ", " + y + "] , killed " + kills;
        
    

    private static int countPossibleKills(int x, int y) 
        int count = 0;
        for (int row = x - 1; row <= x + 1; row++) 
            for (int col = y - 1; col <= y + 1; col++) 
                try 
                    if (BOARD[row][col] > 0) count++;
                 catch (ArrayIndexOutOfBoundsException ex) /*ignore*/
            
        

        return count;
    

    private static void drop(Point here) 
        for (int row = here.x - 1; row <= here.x + 1; row++) 
            for (int col = here.y - 1; col <= here.y + 1; col++) 
                try 
                    if (BOARD[row][col] > 0) BOARD[row][col]--;
                 catch (ArrayIndexOutOfBoundsException ex) /*ignore*/
            
        

        dropCount++;
        remaining = remaining - here.kills;
        print(here);
    

    public static void solve() 
        while (remaining > 0) 
            Point dropWithMaxKills = new Point(-1, -1, -1);
            for (int i = 0; i < ROWS; i++) 
                for (int j = 0; j < COLS; j++) 
                    int possibleKills = countPossibleKills(i, j);
                    if (possibleKills > dropWithMaxKills.kills) 
                        dropWithMaxKills = new Point(i, j, possibleKills);
                    
                
            

            drop(dropWithMaxKills);
        

        System.out.println("Total dropped: " + dropCount);
    

    private static void print(Point drop) 
        System.out.println(drop.toString());
        for (int[] row : BOARD) 
            System.out.println(Arrays.toString(row));
        

        System.out.println();
    

    public static void main(String[] args) 
        solve();
    


【讨论】:

以上是关于炸弹投放算法的主要内容,如果未能解决你的问题,请参考以下文章

[啊哈算法]重要城市(图的割点)

DSP 投放的基本流程和算法

DSP 投放的基本流程和算法

线上广告投放时,如何基于「时空聚类算法」精准挖掘潜在客群?

线上广告投放时,如何基于「时空聚类算法」精准挖掘潜在客群?

信息流投放成本算法分享!