【BFS】2024D-亲子游戏
【BFS】2024D-亲子游戏
题目描述与示例
本题练习地址:https://www.algomooc.com/problem/P3707
题目描述
宝宝和妈妈参加亲子游戏,在一个二维矩阵(N*N
)的格子地图上,宝宝和妈妈抽签决定各自的位置,地图上每个格子有不同的糖果数量,部分格子有障碍物。
游戏规则是妈妈必须在最短的时间(每个单位时间只能走一步)到达宝宝的位置,路上的所有糖果都可以拿走,不能走障碍物的格子,只能上下左右走。
请问妈妈在最短到达宝宝位置的时间内最多拿到多少糖果(优先考虑最短时间到达的情况下尽可能多拿糖果)。
输入描述
第一行输入为N
,N
标识二维矩阵的大小
之后N
行,每行有N
个值,表格矩阵每个位置的值
其中:
-3
:妈妈-2
:宝宝-1
:障碍>=0
:糖果数(0
表示没有糖果,但是可以走)
输出描述
输出妈妈在最短到达宝宝位置的时间内最多拿到多少糖果,行末无多余空格
备注
地图最大50*50
示例一
输入
4
3 2 1 -3
1 -1 1 1
1 1 -1 2
-2 1 2 3
输出
9
说明
此地图有两条最短路径可到宝宝位置,都是最短路径6
步,但先向下再向左可以拿到9
个糖果
示例二
输入
4
3 2 1 -3
-1 -1 1 1
1 1 -1 2
-2 1 -1 3
输出
-1
说明
此地图妈妈无法到达宝宝位置
解题思路
最短路径长度level
很容易使用BFS计算得到,即计算从妈妈位置(sx, sy)
到宝宝位置(tx, ty)
的BFS搜索层数即可。本题难点在于如何计算最短路径长度level
下能够得到最多糖果。
某一个位置(x, y)
,很可能是可以从不同方向进入多次的,如以下例子,从-3
到-2
的最短路径长度为4
,但有多条路径可以到达终点。
暂时无法在飞书文档外展示此内容
标注搜索层数。
暂时无法在飞书文档外展示此内容
容易发现,对于BFS来说,只有当前层的位置都考虑过了之后(即得知到达该位置最多能拿到多少糖果之后),其下一层的位置才会被考虑。这显然是一种dp思想,满足dp的无后效性。
比如对于位置(1,1)
而言(属于第二层),从上边或者从左边两个方向(属于第一层)均可以进入这个位置。在最短路径的限制条件下,不可能选择从右边或者从下边两个方向(属于第三层)进入这个位置。
暂时无法在飞书文档外展示此内容
因此,为了使得进入位置(1,1)
获得的糖果数尽可能地多,我们会选择从上一层中获得糖果数更多的位置(0,1)
进入这个位置。
构建出相应的二维dp数组。dp[i][j]
表示进入位置(i, j)
时能获得的最多糖果数。
第二次BFS过程只搜索level
层。考虑BFS每一层搜索的过程中,从位置(i, j)
进入位置(ni, nj)
时dp[ni][nj]
的更新,此时dp[i][j]
的值已经从上一层搜索中确定了。构建对应的动态转移方程。若
(ni, nj)
不是孩子位置时。dp[ni][nj] = max(dp[ni][nj], dp[i][j] + grid[ni][nj])
(ni, nj)
是孩子位置时。dp[ni][nj] = max(dp[ni][nj], dp[i][j])
以上述例子为例,最终dp
数组的结果为
暂时无法在飞书文档外展示此内容
在第二次BFS过程的具体实现中,需要解决一个矛盾点
- 每一个
(nx, ny)
只需要从其上一层的某一个(x, y)
入队1
次,这样才能保证不超时 dp[nx][ny]
的更新,必须考虑到其上一层的每一个(x, y)
,这样才能保证dp[nx][ny]
的正确性
其中一种解决方案是,把dp
数组也作为check_list
数组来使用,初始化dp
数组中的每一个元素为-1
。dp[nx][ny] == -1
表示点(nx, ny)
尚未被检查过,尚未入队。
if 0 <= nx < n and 0 <= ny < n and grid[nx][ny] != -1:
if dp[nx][ny] == -1:
q.append((nx, ny))
if grid[nx][ny] != -2:
dp[nx][ny] = max(dp[nx][ny], dp[x][y] + grid[nx][ny])
else:
dp[nx][ny] = max(dp[nx][ny], dp[x][y])
代码
Python
# 题目:【BFS】2023C-亲子游戏
# 分值:200
# 作者:许老师-闭着眼睛学数理化
# 算法:BFS
# 代码看不懂的地方,请直接在群上提问
from collections import deque
from math import inf
# 表示4个方向的方向数组
DIRECTIONS = [(0,1), (1,0), (-1,0), (0,-1)]
# 输入地图边长
n = int(input())
grid = list()
# 循环n行,输入地图
for _ in range(n):
grid.append(list(map(int, input().split())))
for i in range(n):
for j in range(n):
# 找到妈妈所在的位置,作为起点
if grid[i][j] == -3:
sx, sy = i, j
# 找到孩子所在的位置,作为起点
if grid[i][j] == -2:
tx, ty = i, j
# BFS搜索层数,初始化为0,用于表示最短路径长度
level = 0
# 是否找到孩子的标记
isFind = False
# 检查数组
check_list = [[0] * n for _ in range(n)]
check_list[sx][sy] = 1
# 维护BFS过程的队列
q = deque()
q.append((sx, sy))
# 进行第一次BFS,找到最短路径长度level
while q:
qSize = len(q)
# 当前搜索层的每一个节点
for _ in range(qSize):
x, y = q.popleft()
# 如果当前点是孩子,直接退出循环
if grid[x][y] == -2:
isFind = True
break
# 遍历四个方向
for dx, dy in DIRECTIONS:
nx, ny = x+dx, y+dy
# 判断是否越界、是否已进入、在矩阵中的值
if 0 <= nx < n and 0 <= ny < n and check_list[nx][ny] == 0 and grid[nx][ny] != -1:
check_list[nx][ny] = 1
q.append((nx, ny))
if isFind:
break
# 搜索层数+1
level += 1
# 如果第一次BFS过程中,妈妈不能找到孩子,则直接返回-1
if not isFind:
print(-1)
# 否则才可以继续后续过程
else:
# 从起点出发,再做一次BFS
# 类似dp过程,构建一个二维dp数组,
# dp[i][j]表示进入位置(i, j)能获得的最多糖果数
# 如果现在从位置(i, j)进入位置(ni, nj)
# 那么存在动态转移方程如下
# 当(ni, nj)不是孩子位置时
# dp[ni][nj] = max(dp[ni][nj], dp[i][j] + grid[ni][nj])
# 当(ni, nj)是孩子位置时
# dp[ni][nj] = max(dp[ni][nj], dp[i][j])
# 初始化dp数组,dp数组可以起到checklist的作用,因此无需额外构建checklist
dp = [[-1] * n for _ in range(n)]
dp[sx][sy] = 0
# 初始化队列
q = deque()
q.append((sx, sy))
# 再次进行BFS,但由于只能搜索level层,因此需要修改BFS循环条件
while level >= 0:
qSize = len(q)
# 当前搜索层的每一个节点
for _ in range(qSize):
x, y = q.popleft()
# 遍历四个方向
for dx, dy in DIRECTIONS:
nx, ny = x + dx, y + dy
# 判断是否越界、在地图中的值
if 0 <= nx < n and 0 <= ny < n and grid[nx][ny] != -1:
# 此处dp数组作为check_list的作用
# 如果dp[nx][ny]为-1,说明尚未被检查过,可以加入队中
if dp[nx][ny] == -1:
q.append((nx, ny))
# 进行动态转移
if grid[nx][ny] != -2:
dp[nx][ny] = max(dp[nx][ny], dp[x][y] + grid[nx][ny])
else:
dp[nx][ny] = max(dp[nx][ny], dp[x][y])
# 搜索层数-1
level -= 1
# 退出循环后,孩子所在位置(tx, ty)在dp数组中的值dp[tx][ty]即为答案
print(dp[tx][ty])
Java
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Scanner;
public class Main {
static int[][] DIRECTIONS = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[][] grid = new int[n][n];
int sx = 0, sy = 0, tx = 0, ty = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
grid[i][j] = scanner.nextInt();
if (grid[i][j] == -3) {
sx = i;
sy = j;
}
if (grid[i][j] == -2) {
tx = i;
ty = j;
}
}
}
int level = 0;
boolean isFind = false;
int[][] checkList = new int[n][n];
checkList[sx][sy] = 1;
Deque<int[]> queue = new ArrayDeque<>();
queue.offer(new int[]{sx, sy});
while (!queue.isEmpty()) {
int qSize = queue.size();
for (int i = 0; i < qSize; i++) {
int[] current = queue.poll();
int x = current[0];
int y = current[1];
if (grid[x][y] == -2) {
isFind = true;
break;
}
for (int[] dir : DIRECTIONS) {
int nx = x + dir[0];
int ny = y + dir[1];
if (nx >= 0 && nx < n && ny >= 0 && ny < n && checkList[nx][ny] == 0 && grid[nx][ny] != -1) {
checkList[nx][ny] = 1;
queue.offer(new int[]{nx, ny});
}
}
}
if (isFind) {
break;
}
level++;
}
if (!isFind) {
System.out.println(-1);
} else {
int[][] dp = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
dp[i][j] = -1;
}
}
dp[sx][sy] = 0;
queue.clear();
queue.offer(new int[]{sx, sy});
while (level >= 0) {
int qSize = queue.size();
for (int i = 0; i < qSize; i++) {
int[] current = queue.poll();
int x = current[0];
int y = current[1];
for (int[] dir : DIRECTIONS) {
int nx = x + dir[0];
int ny = y + dir[1];
if (nx >= 0 && nx < n && ny >= 0 && ny < n && grid[nx][ny] != -1) {
if (dp[nx][ny] == -1) {
queue.offer(new int[]{nx, ny});
}
if (grid[nx][ny] != -2) {
dp[nx][ny] = Math.max(dp[nx][ny], dp[x][y] + grid[nx][ny]);
} else {
dp[nx][ny] = Math.max(dp[nx][ny], dp[x][y]);
}
}
}
}
level--;
}
System.out.println(dp[tx][ty]);
}
}
}
C++
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const vector<vector<int>> DIRECTIONS = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
int main() {
int n;
cin >> n;
vector<vector<int>> grid(n, vector<int>(n));
int sx = 0, sy = 0, tx = 0, ty = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cin >> grid[i][j];
if (grid[i][j] == -3) {
sx = i;
sy = j;
}
if (grid[i][j] == -2) {
tx = i;
ty = j;
}
}
}
int level = 0;
bool isFind = false;
vector<vector<int>> checkList(n, vector<int>(n, 0));
checkList[sx][sy] = 1;
queue<pair<int, int>> q;
q.push({sx, sy});
while (!q.empty()) {
int qSize = q.size();
for (int i = 0; i < qSize; i++) {
auto current = q.front();
q.pop();
int x = current.first;
int y = current.second;
if (grid[x][y] == -2) {
isFind = true;
break;
}
for (auto dir : DIRECTIONS) {
int nx = x + dir[0];
int ny = y + dir[1];
if (nx >= 0 && nx < n && ny >= 0 && ny < n && checkList[nx][ny] == 0 && grid[nx][ny] != -1) {
checkList[nx][ny] = 1;
q.push({nx, ny});
}
}
}
if (isFind) {
break;
}
level++;
}
if (!isFind) {
cout << -1 << endl;
} else {
vector<vector<int>> dp(n, vector<int>(n, -1));
dp[sx][sy] = 0;
q = queue<pair<int, int>>();
q.push({sx, sy});
while (level >= 0) {
int qSize = q.size();
for (int i = 0; i < qSize; i++) {
auto current = q.front();
q.pop();
int x = current.first;
int y = current.second;
for (auto dir : DIRECTIONS) {
int nx = x + dir[0];
int ny = y + dir[1];
if (nx >= 0 && nx < n && ny >= 0 && ny < n && grid[nx][ny] != -1) {
if (dp[nx][ny] == -1) {
q.push({nx, ny});
}
if (grid[nx][ny] != -2) {
dp[nx][ny] = max(dp[nx][ny], dp[x][y] + grid[nx][ny]);
} else {
dp[nx][ny] = max(dp[nx][ny], dp[x][y]);
}
}
}
}
level--;
}
cout << dp[tx][ty] << endl;
}
return 0;
}
时空复杂度
时间复杂度:O(N^2)
。
空间复杂度:O(N^2)
。