炸弹投放算法
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();
【讨论】:
以上是关于炸弹投放算法的主要内容,如果未能解决你的问题,请参考以下文章