📅  最后修改于: 2023-12-03 14:58:33.572000             🧑  作者: Mango
本题是Sudo GATE 2020 Mock III的第35题,题目名称为"门"。这是一道需要设计算法的问题,涉及到图论中的最短路径问题。
假设你正在玩一款类似迷宫的游戏,游戏中有很多门以及与之对应的钥匙。每扇门都有一个特定的编号,而每把钥匙也都有一个特定的编号。你开始游戏时没有任何钥匙,只能进行走路操作。当你到达一扇锁着的门时,如果你没有这扇门所用的钥匙,则需要通过其他路径到达,并且不能参与得分。一旦你获得钥匙,你就可以打开对应的门。如果你到达某个区域并没有任何门需要解锁,那么你会获得一定数额的分数。你的目标是获得最大化的总分数。
给定一个由节点和边组成的图,节点可以分为门,钥匙和没有门和钥匙的普通节点。每扇门都与一个唯一编号的钥匙相对应。门和钥匙用大写字母表示,节点用小写字母表示。已知源节点 S 和终点节点 T,请你计算从节点 S 到节点 T 的路径中能获得的最大分数。
你可以假设给定的图形满足一下条件:
对于每个测试用例,输出一个整数,表示能够获得的最大分数。
以下是一个输入/输出示例:
输入: 2 3 3 a.A @.B c.. 0 0
3 3 a.A
C.. 0 0
输出: 0 -1
本题需要使用图论中的最短路径算法解决。一般来说,最短路径算法可以分为两类:单源最短路径和全源最短路径。单源最短路径和本题较为相似,因此可以选择使用单源最短路径算法来解决本题。
我们可以先创建一个graph数组,将输入的字母矩阵转换成一个邻接矩阵存储,再基于Dijkstra算法求解最短路径长度。具体步骤如下:
1.初始化起始点到各顶点的距离为正无穷,S点到S点的距离为0,将S点加入优先队列。 2.从优先队列中取出路径最短的点u,然后遍历它的邻居节点,更新邻居节点的最短路径长度,如果该点没有访问过加入队列中。 3.重复上述过程直到队列为空,此时就能得到从S点到各点的最短路径长度。
一般情况下,在一个图中,存在两个节点之间多条路径的情况,因此需要使用一个heapq(堆)来维护路程的最短长度,优先考虑路程较短的路径。
算法分三步:
1.先将所有的钥匙点作为源点,分别求出每个钥匙点到其他点的最短距离,得到一个距离表,表示各点与不同钥匙的距离,将此表存储到dist中。 2.将dist中的表合并成一个总的dist,此表由S点,所有钥匙点和所有门点组成。 3.针对总的距离表,使用Dijkstra算法求解最短距离。
import heapq
import sys
"""
将字符矩阵转换为图中的节点和边
"""
def create_graph(R, C, graph):
count_key = 0
nodes = {}
node_keys = {}
# 转换前图像
print("Before transformation")
print(graph)
# 转换后的图像
for i in range(R):
for j in range(C):
node = graph[i][j]
if node != "#":
if node == "@": # 设置起点
start = (i, j)
elif node.islower():
node_keys[node] = (i, j) # 添加钥匙节点
if count_key < (ord(node) - 96):
count_key = ord(node) - 96 # 记录字母的最大号码值
elif node.isupper():
if node in nodes.keys():
nodes[node].append((i, j))
else:
nodes[node] = [(i, j)]
else:
continue
# 添加所有的门节点
for node, points in nodes.items():
for point in points:
graph[point[0]][point[1]] = node
# 测试
print("After transformation")
print(graph)
# 添加所有的边
edges = {}
(dx, dy) = (1, 0, -1, 0), (0, 1, 0, -1) # 定义方向数组
for i in range(R):
for j in range(C):
node = graph[i][j]
if node != "#":
if node.islower(): # 添加钥匙到门的边
dists = bfs_path((i, j), node_keys, graph)
for key, value in dists.items():
if key.isupper():
if edges.get(node) is None:
edges[node] = [(key, value)]
else:
edges[node].append((key, value))
elif node.isupper(): # 添加门到钥匙或其他门的边
if edges.get(node) is not None:
continue
node_list = [(i, j)]
dists = bfs_path((i, j), node_list, graph) # 查找所有连通点
for key, value in dists.items():
if key.isupper() or key == ".": # 过滤
continue
if edges.get(key) is None:
edges[key] = [(node, value)]
else:
edges[key].append((node, value))
# 查找其他节点的边
indx = {} # 索引
for i in range(count_key):
indx[i + 1] = chr(96 + i + 1)
for node in node_keys:
dists = bfs_path(node_keys[node], node_keys, graph)
for key, value in dists.items():
if key == node or key == ".":
continue
if key.isupper():
gates = [(edge[0], edge[1]) for edge in edges[key]] # 门的所有边
for i in range(len(gates)):
(gate, weight) = gates[i]
if gate != node: # 过滤相同的点
weight += value
if edges.get(node) is None:
edges[node] = [(gate, weight)]
else:
edges[node].append((gate, weight))
else:
indx_key = indx.get(ord(key) - 96)
if edges.get(indx_key) is None:
edges[indx_key] = [(key, value)]
else:
edges[indx_key].append((key, value))
return start, edges
"""
使用Dijkstra算法求解最短路径
"""
def dijkstra(start, dest, edges):
heap = [(0, start, [])]
visited = {}
while heap:
(cost, pos, path) = heapq.heappop(heap)
if pos in visited:
continue
visited[pos] = cost # 记录到各点的最短路径
path = path + [pos]
if pos == dest:
return visited[pos]
# 循环添加所有未访问过的边
for next_pos, next_cost in edges.get(pos, []):
if next_pos in visited:
continue
heapq.heappush(heap, (cost + next_cost, next_pos, path))
return sys.maxsize
"""
从点源src遍历节点中键值为keys,到各点距离
"""
def bfs_path(src, keys, graph):
dists = {}
q = [src]
visited = set(src)
steps = 0
while q:
size = len(q)
for i in range(size):
node = q.pop(0)
if graph[node[0]][node[1]].islower() and node != src:
dists[graph[node[0]][node[1]]] = steps
for j in range(4):
x, y = node[0] + dx[j], node[1] + dy[j]
if x < 0 or x >= len(graph) or y < 0 or y >= len(graph[0]) or graph[x][y] == "#" or (x, y) in visited:
continue
q.append((x, y))
visited.add((x, y))
steps += 1
return dists
"""
求解最大分数
"""
def get_max_score(test_cases):
results = []
for (R, C, graph, sr, sc) in test_cases:
start, edges = create_graph(R, C, graph)
total_dist_key = {} # 起点到所有其他节点的距离
for indx, pos in enumerate(chr(x + 97) for x in range(len(edges))):
next_edges = dict(edges)
for edge in edges[indx]:
if edge[0].isupper():
next_edges[indx].remove(edge) # 删除无钥匙的门
for key, value in next_edges.items():
if key == indx:
continue
for i in range(len(value)):
p, w = value[i]
if p == indx:
value[i] = ("@", w) # 添加起点到所有钥匙的边
next_edges[key] = value
dists = {indx: 0} # 所有节点的最短路径
for key in next_edges.keys():
if key == indx:
continue
dists[key] = dijkstra((sr, sc), key, next_edges)
total_dist_key[pos] = dists
dists = {}
indx = {} # 索引
for i in range(len(edges)):
indx[i] = chr(i + 97)
for node in total_dist_key:
next_edges = dict(edges)
if next_edges.get(node) is not None:
next_edges.pop(node) # 删除所有与钥匙点相连的边
for key, value in next_edges.items():
for i in range(len(value)):
p, w = value[i]
if p == node and p.isupper():
value[i] = (".", w) # 添加门到通路的边
next_edges[indx[len(indx) - 1]] = [(node, 0)] # 添加终点到所有节点的边,距离为0
dists[node] = dijkstra("@", indx[len(indx) - 1], next_edges)
result = sum([value.get(".", sys.maxsize) for value in dists.values() if value.get(".", sys.maxsize) < sys.maxsize])
if result == sys.maxsize:
result = -1
results.append(result)
return results