ou 救命,是炸、炸弹人

112 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第24天,点击查看活动详情

在大家小时候不知道有没有玩过下图所示游戏

image.png 在这里我们可以看出来,图上有一个黑色炸弹,两种墙,和一些怪兽,那么我们要在哪里放置炸弹才能消灭最多怪兽呢?

我们可以把地图模型化:墙用‘#’表示,怪兽用“@”表示,空地用‘.’表示,炸弹只能在空地上。

首先我们用一个二维数组来存储地图,那么炸弹位置我们可以一个一个尝试。已知爆炸方向沿着上下左右四个方向,那么我们可以对每个点枚举,然后统计上下左右有几个怪兽,最后输出最多的一条路。

怎么判断一个方向上的消灭怪兽判断?

while(a[x][y]!='#'){
if(a[x][y]=='@'){
sum++}
x++;
}

定义地图数组a,里面x表示第x行,y表示第y列,那么当当前值不是墙,并且该位置有怪物,那么怪物数增加,往下一行继续判断,直到碰到墙。

image.png

那么代码是不是有思路了呢。

首先声明一个字符数组,然后通过键盘或者直接赋值得到地图,接下来进行循环遍历,如果当前是空地,就可以尝试放炸弹,进行一个方向上消灭怪兽判断,包括四个:上下左右,代码都是类似的

image.png

但是在这种代码里面,我们可能遇到一个问题,就是小人没办法到达那个点。所以我们还可以通过广度优先来枚举.

伪算法:

初始位置入队列,标记初始位置已经走过置为1,判断该位置的炸弹最大威力并记录

开始上下左右循环,每一次都以队列首元素为初始出发点,扩展位置入队列(扩展时也需要判断是否越界,能否安装炸弹以及是否已经走过)满足条件则判断该点的最大威力 与当前最大威力对比,大于则替换之

然后队首元素出队,如此往复循环

伪代码:

首先,我们的初始点为(3,3)我们需要将其标记置为1,表示该点已经走过,判断炸弹在该点的威力,然后赋值给最大当前威力,并保存该点,

开始循环向上下左右四个方向分别尝试

{

判断扩展的点是否越界,查出地图,查出则进行下一个方向的尝试

判断该点是否已经走过并且判断该点是否平地(只有平地才能放炸弹),如果该点没有走过且可以放炸弹

则以此点为初始点 进一步 按原策略搜索

}

搜索完毕后返回

按照伪代码:那么dfs如下:

 public static void dfs(int x, int y) {
        int sum, tx, ty;
        //四个方向
        int[][] next = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
        //得到当前位置消灭敌人的数量
        sum = getsum(x, y);
        //更新最大消灭敌人的数量
        if (sum > max) {
            max = sum;
            mx = x;
            my = y;
        }
        for (int i = 0; i < 4; i++) {
            tx = x + next[i][0];
            ty = y + next[i][1];
            if (tx < 0 || tx > n - 1 || ty < 0 || ty > m - 1) {
                continue;
            }
            if (a[tx][ty] == '.' && book[tx][ty] == 0) {
                book[tx][ty] = 1;
                dfs(tx, ty);
            }
        }
        return;
    }

getsum 其实就是判断一个方向上怪兽

image.png

广度优先搜索的精髓在于不一条路走到黑,而是广泛的考虑下一步走到的每一个点,而这样的方式可以很好的得到类似最小值最短路径这种问题,像是一层一层的向下走,可以类比从内向外拨开洋葱皮一样,当你到达你的目的时,显然这就时最短的路径,因为所有扩展路径先前走过的长度都是一样的。