📜  门|门 CS 1996 |问题 26(1)

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

门|门 CS 1996 | 问题 26

这道题目是一个经典问答机制的算法问题,它通常被用作数据结构和算法的练习题。正确理解题目并以正确方式编写出代码解决问题,将有助于加深你的编程和算法知识。

问题描述

有一排相邻的 N 扇门(标号为 1~N),每扇门均可以选择打开或关闭。现在有 M 个人,每个人都会走过这排门(从第一扇门走到第 N 扇门),并且每个人都有一个门的集合,表示他可以通过哪些门,为 1 表示可以通过,0 表示不可以通过。当一位人经过一扇门时,他会尝试将它打开。如果门已经被打开,那么他会保持这扇门的状态并向下继续走;如果门没有被打开,那么他会对这扇门进行锁定(即打开它),并向下继续走。当这个人走完所有的门时,他会逆序传回,再次经过这排门,并将他遇到的所有门状态恢复到原来的状态。

现在我们想知道,最少需要多少个人才能保证所有的门都被打开了,并且在每个人经过之后,所有的门都可以恢复到原来的状态。

输入格式

第一行两个整数 N 和 M,表示门数量和人数。

接下来 M 行,每行两个整数 k 和 s,表示这个人可以通过的门的数量和他所能通过的门的编号。

输出格式

一个整数,表示最少需要多少个人才能保证所有的门都被打开了,并且在每个人经过之后,所有的门都可以恢复到原来的状态。

输入样例
5 5
2 1 2
2 2 3
2 3 4
2 4 5
2 1 5
输出样例
2
解题思路

首先,我们可以发现,我们需要找到一种能够覆盖所有门的集合,使得每个人至少包含其中一门门的集合。并且,我们需要找到一种能够保证,所有人经过之后,所有门都可以回到原来的状态。

根据上述分析,我们可以使用贪心算法来解决这个问题。首先,我们需要对每个门求一个掩码(mask),假设一共有 10 扇门(从 0 到 9),那么门 0 的掩码为 1 << 0,门 1 的掩码为 1 << 1,以此类推。接下来,我们从第一扇门开始枚举,对于每扇门,我们求出它的掩码并记为 mask,接着对于每个人,将他可以通过的门与 mask 进行按位与操作,并将结果累加起来,得到集合 S。如果所有门均被表示在集合 S 中,则可以认为这个人可以覆盖到该门。

接下来,我们可以使用一个 hash 表,将所有能覆盖到某门的人的编号存储下来。对于每扇门,我们枚举所有覆盖到该门的人,并将他们的编号存入集合中。最后,我们需要找到能够覆盖所有门的最小的一组人。考虑使用集合覆盖算法即可。

参考代码
# 门|门 CS 1996 | 问题 26
# https://www.acwing.com/problem/content/1998/
# 思路:使用贪心算法,依次遍历每扇门,找到能够覆盖该门的人,在所有能够覆盖所有门的人中选择最小的一个。
from typing import List


def door_cover(n: int, m: int, doors: List[List[int]]) -> int:
    # 求掩码
    mask = [1 << i for i in range(n)]

    # 构建 hash 表,key 为门编号,value 为能够覆盖该门的人的编号
    doors_hash = {}
    for i in range(n):
        doors_hash[i] = set()
    for i in range(m):
        k, s = doors[i]
        for j in s:
            doors_hash[j-1].add(i)

    # 贪心算法,依次遍历每扇门,并将能够覆盖该门的人的编号存入集合中
    persons_cover = set(range(m))
    doors_cover = set(range(n))
    for i in range(n):
        persons_cover_temp = set()
        for j in doors_hash[i]:
            if len(persons_cover & {j}) > 0:
                persons_cover_temp.add(j)
        doors_cover &= set([mask[i] | sum([mask[i] & doors[i][1] for i in doors_cover & persons_cover_temp])])
    if len(doors_cover) == 0:
        return -1

    # 集合覆盖算法,用来寻找覆盖所有门的最小的一组人
    persons = set()
    while len(doors_cover) > 0:
        cnt = -1
        key = -1
        for k, v in doors_hash.items():
            if len(v & persons_cover) > cnt:
                cnt = len(v & persons_cover)
                key = k
        if cnt == 0:
            return -1
        doors_cover -= set([key])
        persons_cover &= doors_hash[key]
        persons |= persons_cover
    return len(persons)