📜  门| GATE-CS-2017(Set 2)|问题19(1)

📅  最后修改于: 2023-12-03 15:28:45.316000             🧑  作者: Mango

这是一道来自 GATE-CS-2017(Set 2) 的问题(题目编号为 19),考察的是对于优先队列的理解和应用。

题目描述

有 $n$ 个门 "G1", "G2", ..., "Gn",开始时它们都处于关闭状态。有 $m$ 个人 "P1", "P2", ..., "Pm",同时到达门前,每个人都带了一把钥匙。有的钥匙可以打开某些门,但不一定打开所有门,门也不一定只能被某一把钥匙打开。同时,每个人前来的目的也可能不同,有的人想要进入某个特定的房间,有的人只是想打开某个特定的门。

每个人排队进入门前,当一个人刚刚进入队列时,他会给你一张纸条,上面写着他的名字和他持有的钥匙可以打开哪些门。

给定所有人的信息,输出以下内容:

  1. 开哪些门可以保证所有人都能够到达他们的目标。

  2. 可能会有一些人到达不了目标,请列出他们的名字。

注意:所有数据输入都是合法的,不需要考虑异常情况。

样例

假如有下面这些钥匙和目标:

| 人名 | 钥匙 | 目标 | | ---- | ---- | ---- | | P1 | G1, G2 | G3 | | P2 | G1, G3 | G2 | | P3 | G1, G2, G3 | G1 | | P4 | G4 | G3 |

那么输出的内容应当是:

  1. 开门:G1, G2, G3

  2. 不会到达目标的人有:P4

分析

这个问题可以用最短路径算法解决,即使用 Dijkstra ,寻找所有目标间相互到达的最短距离。

可以将每一个人看做一个起点,将每一扇门看做一个终点,计算出每个人到每一扇门的距离。在计算距离的过程中,需要注意以下两点:

  1. 每一个人可以使用多把钥匙,能开启当前所在门的所有钥匙都可以使用。

  2. 如果当前门已经被开启,则不需要再次打开。

在计算出每个人到每扇门的距离后,就可以判断哪些门需要被开启,可以被所有人到达。同时,找到不能够到达目标门的人的名字。

代码
from queue import PriorityQueue

class Door:
    def __init__(self):
        self.keys = set()    # 此门的钥匙
        self.target = False  # 是否为目标门
        self.distance = {}   # 距离
        self.opened = False  # 是否已经被打开

class Person:
    def __init__(self, name, keys, target):
        self.name = name
        self.keys = keys.split(',')
        self.target = target

def find_doors(persons):
    doors = {}
    for person in persons:
        for key in person.keys:
            if key not in doors:
                doors[key] = Door()
            doors[key].keys.add(key)
        if person.target not in doors:
            doors[person.target] = Door()
            doors[person.target].target = True
    return doors

def dijkstra(start, doors):
    min_heap = PriorityQueue()
    for door in doors.values():
        door.distance = {start: 0}
        min_heap.put((0, door))
    while not min_heap.empty():
        _, door = min_heap.get()
        for key in door.keys:
            next_door = doors[key]
            if not next_door.opened:
                for person, distance in door.distance.items():
                    if person not in next_door.distance:
                        next_distance = distance + 1
                        next_door.distance[person] = next_distance
                        min_heap.put((next_distance, next_door))
        door.opened = True

def find_open_doors(persons):
    doors = find_doors(persons)
    dijkstra('start', doors)
    result = set()
    for door in doors.values():
        if door.target:
            distances = door.distance.values()
            if all(distance is not None for distance in distances):
                result.add(''.join(sorted(door.keys)))
    return result

def find_not_reached_persons(persons):
    doors = find_doors(persons)
    dijkstra('start', doors)
    result = []
    for person in persons:
        distance = doors[person.target].distance.get(person, None)
        if distance is None:
            result.append(person.name)
    return result

persons = [
    Person('P1', 'G1,G2', 'G3'),
    Person('P2', 'G1,G3', 'G2'),
    Person('P3', 'G1,G2,G3', 'G1'),
    Person('P4', 'G4', 'G3'),
]

open_doors = find_open_doors(persons)
not_reached_persons = find_not_reached_persons(persons)

print("开门:", ', '.join(open_doors))
print("不能到达目标的人有:", ', '.join(not_reached_persons))

代码中用到了 queue.PriorityQueue 。由于队列的方法名和 Python 的关键字有重叠,为了避免冲突,在引入时使用了 from 。由于 Python 本身对于多线程和进程的支持库非常丰富,所以在实现这个算法时也可以考虑使用多线程的方式,提高计算效率。