📌  相关文章
📜  门| Sudo GATE 2020 Mock III(2019 年 1 月 24 日)|第 35 题(1)

📅  最后修改于: 2023-12-03 14:58:33.572000             🧑  作者: Mango

题目介绍

本题是Sudo GATE 2020 Mock III的第35题,题目名称为"门"。这是一道需要设计算法的问题,涉及到图论中的最短路径问题。

题目描述

假设你正在玩一款类似迷宫的游戏,游戏中有很多门以及与之对应的钥匙。每扇门都有一个特定的编号,而每把钥匙也都有一个特定的编号。你开始游戏时没有任何钥匙,只能进行走路操作。当你到达一扇锁着的门时,如果你没有这扇门所用的钥匙,则需要通过其他路径到达,并且不能参与得分。一旦你获得钥匙,你就可以打开对应的门。如果你到达某个区域并没有任何门需要解锁,那么你会获得一定数额的分数。你的目标是获得最大化的总分数。

给定一个由节点和边组成的图,节点可以分为门,钥匙和没有门和钥匙的普通节点。每扇门都与一个唯一编号的钥匙相对应。门和钥匙用大写字母表示,节点用小写字母表示。已知源节点 S 和终点节点 T,请你计算从节点 S 到节点 T 的路径中能获得的最大分数。

你可以假设给定的图形满足一下条件:

  • 所有的门都是唯一的。
  • 所有的门没有必要按照任何特定的顺序解锁。
  • 每把钥匙只能使用一次。

输入格式

  • 第一行包含一个整数 T,表示测试用例的数量。
  • 对于每个测试用例:
    • 第一行包含两个整数 R 和 C,表示行数和列数。
    • 接下来 R 行,每行包含 C 个字符,表示对应位置的节点。字符集中包含小写字母,大写字母以及 ".",其中 "." 表示普通节点,大写字母表示门,小写字母表示钥匙。
    • 最后一行包含两个整数 sr 和 sc,表示 S 节点的位置。
    • 中间有一个空行,表示一个测试用例结束。

输出格式

对于每个测试用例,输出一个整数,表示能够获得的最大分数。

示例

以下是一个输入/输出示例:

输入: 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