Skip to content

04115: 鸣人和佐助

bfs, http://cs101.openjudge.cn/practice/04115/

佐助被大蛇丸诱骗走了,鸣人在多少时间内能追上他呢?

image-20231025141449508

已知一张地图(以二维矩阵的形式表示)以及佐助和鸣人的位置。地图上的每个位置都可以走到,只不过有些位置上有大蛇丸的手下,需要先打败大蛇丸的手下才能到这些位置。鸣人有一定数量的查克拉,每一个单位的查克拉可以打败一个大蛇丸的手下。假设鸣人可以往上下左右四个方向移动,每移动一个距离需要花费1个单位时间,打败大蛇丸的手下不需要时间。如果鸣人查克拉消耗完了,则只可以走到没有大蛇丸手下的位置,不可以再移动到有大蛇丸手下的位置。佐助在此期间不移动,大蛇丸的手下也不移动。请问,鸣人要追上佐助最少需要花费多少时间?

输入

输入的第一行包含三个整数:M,N,T。代表M行N列的地图和鸣人初始的查克拉数量T。0 < M,N < 200,0 ≤ T < 10 后面是M行N列的地图,其中@代表鸣人,+代表佐助。*代表通路,#代表大蛇丸的手下。

输出

输出包含一个整数R,代表鸣人追上佐助最少需要花费的时间。如果鸣人无法追上佐助,则输出-1。

样例输入

样例输入1
4 4 1
#@##
**##
###+
****

样例输入2
4 4 2
#@##
**##
###+
****

样例输出

样例输出1
6

样例输出2
4

思路:稍复杂的bfs问题。visited需要维护经过时的最大查克拉数,只有大于T值时候才能通过,然后就是常见bfs。

python
# 夏天明 元培学院

from collections import deque

M, N, T = map(int, input().split())
graph = [list(input()) for i in range(M)]
direc = [(0,1), (1,0), (-1,0), (0,-1)]
start, end = None, None
for i in range(M):
    for j in range(N):
        if graph[i][j] == '@':
            start = (i, j)
def bfs():
    q = deque([start + (T, 0)])
    visited = [[-1]*N for i in range(M)]
    visited[start[0]][start[1]] = T
    while q:
        x, y, t, time = q.popleft()
        time += 1
        for dx, dy in direc:
            if 0<=x+dx<M and 0<=y+dy<N:
                if (elem := graph[x+dx][y+dy]) == '*' and t > visited[x+dx][y+dy]:
                    visited[x+dx][y+dy] = t
                    q.append((x+dx, y+dy, t, time))
                elif elem == '#' and t > 0 and t-1 > visited[x+dx][y+dy]:
                    visited[x+dx][y+dy] = t-1
                    q.append((x+dx, y+dy, t-1, time))
                elif elem == '+':
                    return time
    return -1
print(bfs())
python
# 2300011075	苏王捷	工学院
'''
这段代码是一个迷宫求解的问题。主要使用了广度优先搜索算法来找到从起点到终点的最短路径。

首先,代码导入了deque模块来实现队列数据结构。
然后,定义了一个Node类,表示迷宫中的一个节点。它有四个属性:x和y表示节点的坐标,
tools表示节点当前拥有的工具数,steps表示从起点到达该节点的步数。

接下来,读取输入的迷宫信息,包括迷宫的大小M和N,以及可以使用的工具数T。maze是一个二维列表,
表示迷宫的格子,其中'@'表示起点,'+'表示终点,'*'表示障碍物。

创建了一个visit列表,用于记录节点是否被访问过。visit是一个三维列表,
三个维度分别表示行、列和工具数。

定义了directions列表,包含四个方向的偏移量。

通过遍历迷宫,找到起点和终点的位置,并设置起点节点的属性。

使用广度优先搜索算法,通过一个队列queue来依次处理节点。从起点开始,判断四个方向的相邻节点:
如果是'*'表示可以直接通过,将其加入队列并标记为已访问;如果是'#'表示需要使用一个工具,
才能通过,判断当前节点是否还有工具可用,如果有则减少一个工具,并将该节点加入队列并标记为已访问。

如果队列为空,即无法到达终点,则输出"-1";如果找到终点,输出步数,并将flag标记为1,退出循环。

最后,判断flag是否为0,如果是说明无法找到终点,输出"-1"。
'''

from collections import deque


class Node:
    def __init__(self, x, y, tools, steps):
        self.x = x
        self.y = y
        self.tools = tools
        self.steps = steps


M, N, T = map(int, input().split())
maze = [list(input()) for _ in range(M)]
visit = [[[0]*(T+1) for _ in range(N)] for _ in range(M)]
directions = [[-1, 0], [1, 0], [0, -1], [0, 1]]
start = end = None
flag = 0
for i in range(M):
    for j in range(N):
        if maze[i][j] == '@':
            start = Node(i, j, T, 0)
            visit[i][j][T] = 1
        if maze[i][j] == '+':
            end = (i, j)
            maze[i][j] = '*'
            
queue = deque([start])
while queue:
    node = queue.popleft()
    if (node.x, node.y) == end:
        print(node.steps)
        flag = 1
        break
    for direction in directions:
        nx, ny = node.x+direction[0], node.y+direction[1]
        if 0 <= nx < M and 0 <= ny < N:
            if maze[nx][ny] == '*':
                if not visit[nx][ny][node.tools]:
                    queue.append(Node(nx, ny, node.tools, node.steps+1))
                    visit[nx][ny][node.tools] = 1
            elif maze[nx][ny] == '#':
                if node.tools > 0 and not visit[nx][ny][node.tools-1]:
                    queue.append(Node(nx, ny, node.tools-1, node.steps+1))
                    visit[nx][ny][node.tools-1] = 1
                    
if not flag:
    print("-1")

用一个特殊的bfs,除了记录位置,还要记录剩下的查克拉数量,某个位置能走的前提是,这次走到这个位置所剩下的查克拉,要比上一次来的时候多(初始为-1),其他的和正常bfs相同

python
# 钟明衡 物理学院
m, n, t = map(int, input().split())
M = [input() for _ in range(m)]
x = y = f = 0
for i in range(m):
    for j in range(n):
        if M[i][j] == '@':
            x, y = i, j
            f = 1
            break
    if f:
        break
s, e = 0, 1
l = [(x, y, t)]
g = [[-1]*n for i in range(m)]
g[x][y] = t
c = 0
dx, dy = [1, 0, -1, 0], [0, 1, 0, -1]
while s != e:
    c += 1
    for i in range(s, e):
        for j in range(4):
            x, y, k = l[i][0]+dx[j], l[i][1]+dy[j], l[i][2]
            if x in (-1, m) or y in (-1, n):
                continue
            if M[x][y] == '+':
                print(c)
                exit()
            elif M[x][y] == '#':
                if k-1 > g[x][y]:
                    g[x][y] = k-1
                    l.append((x, y, k-1))
            elif M[x][y] == '*':
                if k > g[x][y]:
                    g[x][y] = k
                    l.append((x, y, k))
    s, e = e, len(l)
print(-1)

主要是对bfs作修改,在过程中点是可以重复走的,只有查克拉数大于之前该点的查克拉数才能再次走该点,这样能保证不丢失解,用时就是看第几层遇到了‘+’

python
# 王昊 光华管理学院
def dijkstra(g, start_x, start_y, T):
    queue = [(start_x, start_y, T)]
    cost = 0
    energy = [[-1] * N for _ in range(M)]
    energy[start_x][start_y] = T
    directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]

    start, end = 0, 1
    while start != end:
        cost += 1
        for i in range(start, end):
            current_x, current_y, currentEng = queue[i]
            for x, y in directions:
                next_x = current_x + x
                next_y = current_y + y

                if 0 <= next_x < M and 0 <= next_y < N:
                    if g[next_x][next_y] == '+':
                        print(cost)
                        exit()
                    elif g[next_x][next_y] == '#':
                        if currentEng - 1 > energy[next_x][next_y]:
                            energy[next_x][next_y] = currentEng - 1
                            queue.append((next_x, next_y, currentEng - 1))
                    elif g[next_x][next_y] == '*':
                        if currentEng > energy[next_x][next_y]:
                            energy[next_x][next_y] = currentEng
                            queue.append((next_x, next_y, currentEng))
        start, end = end, len(queue)


M, N, T = map(int, input().split())
g = []
start_x = 0
start_y = 0
for i in range(M):
    g.append(list(input()))
    if '@' in g[i]:
        start_x = i
        start_y = g[i].index('@')

dijkstra(g, start_x, start_y, T)
print(-1)

bfs时在visited里面存储能量值(不同能量值可能多次通过同一位置)。

python
# 高铭泽 物理学院
from collections import deque

dire = [(-1, 0), (0, -1), (1, 0), (0, 1)]
flag = 0
ans = 0


def bfs(x, y, t):
    visited = set()
    global ans, flag
    q = deque()
    q.append((t, x, y, 0))
    while q:
        t, x, y, ans = q.popleft()
        for dx, dy in dire:
            nx = x + dx
            ny = y + dy
            if 0 <= nx < m and 0 <= ny < n:
                if g[nx][ny] != "#":
                    nt = t
                else:
                    nt = t - 1
                if nt >= 0 and (nt, nx, ny) not in visited:

                    newans = ans + 1
                    if g[nx][ny]=="+":
                        flag = 1
                        return flag,newans
                    q.append((nt, nx, ny, newans))
                    visited.add((nt, nx, ny))
    return flag,ans


m, n, t = map(int, input().split())
g = []
for i in range(m):
    g.append(list(input()))
for i in range(m):
    for j in range(n):
        if g[i][j] == "@":
            x = i
            y = j
flag,newans=bfs(x, y, t)
if flag:
    print(newans)
else:
    print(-1)

思路:使用bfs,令有visited字典保存走过位置的最大chakra,防止冗余

python
#段明远 元培
from collections import deque
di = [1,0,0,-1]; dj = [0,1,-1,0]

def bfs(mat, M, N, T):
    found = 0
    for i in range(M):
        if found:
            break
        for j in range(N):
            if mat[i][j] == '@':
                naruto_i = i
                naruto_j = j
                found = 1
                break

    if mat[naruto_i][naruto_j] == '+':
        return 0

    queue = deque([(naruto_i, naruto_j, T)])
    step = 0
    visited = {(naruto_i, naruto_j): T}
    while queue:
        step += 1
        for _ in range(len(queue)):
            i, j, chakra = queue.popleft()
            for k in range(4):
                if 0<=i+di[k]<M and 0<=j+dj[k]<N and (chakra or mat[i+di[k]][j+dj[k]] != '#') and visited.get((i+di[k], j+dj[k]), -1) < chakra:
                    if mat[i+di[k]][j+dj[k]] == '+':
                        return step
                    queue.append((i+di[k], j+dj[k], chakra - int(mat[i+di[k]][j+dj[k]] == '#')))
                    visited[(i+di[k], j+dj[k])] = chakra - int(mat[i+di[k]][j+dj[k]] == '#')
    return -1

M, N, T = map(int, input().split())
mat = []
for i in range(M):
    mat.append(list(input()))
ans = bfs(mat, M, N, T)
print(ans)

思路:heap+bfs

python
# 23物院宋昕杰
from heapq import *
m, n, t = map(int, input().split())
matrix = [[-1]*(n + 2)] + [[-1] + list(input()) + [-1] for _ in range(m)] + [[-1]*(n + 2)]
heap = []
x, y = 0, 0
for i in range(1, m + 1):
    for j in range(1, n + 1):
        if matrix[i][j] == '@':
            x, y = i, j
            break
heap.append((0, x, y, t))
heapify(heap)
searched = {}
while heap:
    time, x, y, left = heappop(heap)
    if matrix[x][y] == '+':
        print(time)
        exit()
    elif matrix[x][y] == '#':
        left -= 1
        if left < 0:
            continue
    if matrix[x][y] == -1:
        continue
    if (x, y) in searched:
        if searched[(x, y)] >= left:
            continue
    searched[(x, y)] = left
    time += 1
    heappush(heap, (time, x + 1, y, left))
    heappush(heap, (time, x - 1, y, left))
    heappush(heap, (time, x, y + 1, left))
    heappush(heap, (time, x, y - 1, left))
print(-1)