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

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

门 | Sudo GATE 2020 Mock III(2019 年 1 月 24 日)|第 42 题

题目描述

一家工厂需要生产一批门,每扇门由 n 个材料组成,第 i 个材料需要 ai 的时间来生产。为了保证门的质量,必须按照一定的顺序进行生产,即只有在前面所有门的材料都已经准备好的情况下才能生产下一扇门。现在给定材料的生产时间,请确定是否可以按照要求完成所有门的生产。如果可以,给出一个生产顺序。

函数原型为:

def can_produce_doors(n: int, m: int, needs: List[List[int]]) -> Tuple[bool, List[int]]:
输入格式

第一行包含两个整数 n、m,分别表示门的数量和材料的数量。

接下来 n 行,每行 m 个整数,表示每扇门需要的材料的时间。

输出格式

如果不可能完成所有门的生产,返回 (False, [])。

如果可以完成所有门的生产,返回一个元组 (True, order),其中 order 是一个长度为 n 的排列,表示按照要求完成所有门的生产的顺序。如果有多种方案,返回其中任意一种方案即可。

样例输入
3 3
1 3 5
2 3 8
3 5 1
样例输出
(True, [3, 1, 2])
提示

在此题中,如果不能完成所有门的生产,千万不要忘记返回 (False, [])。

解题思路

这是一道典型的拓扑排序问题。

首先,我们需要构建一个有向图来描述材料之间的依赖关系。具体来说,我们可以将每个材料看作一个节点,如果材料 i 需要在门 j 中使用,那么我们就从 i 到 j 连接一条有向边。这样,我们就得到了一张包含 n + m 个节点的有向图,其中前 n 个点对应门,后 m 个点对应材料。特别的,我们在最前面放置一个超级源点,从源点连向所有门的节点。

为了保证前置条件的满足,我们需要按拓扑排序的顺序完成节点的遍历。具体来说,我们首先对整张图跑一遍拓扑排序算法,找到一个任意的合法遍历顺序,然后按照这个顺序依次处理每个节点。每次处理时,我们枚举所有子节点,然后拿到子节点对应的材料的完成时间的最大值+子节点到门的时间,求出这些值的最大值即可。维护一下每个节点的完成时间,不断更新即可完成所有门的生产的计算。

如果计算结束后仍有未被处理的节点,则说明存在无法满足的前置条件,返回 (False, []) 即可。

实现代码
from typing import List, Tuple


def can_produce_doors(n: int, m: int, needs: List[List[int]]) -> Tuple[bool, List[int]]:
    # 构建有向图
    graph = []
    for i in range(n + m + 1):
        graph.append([])
    for i in range(n):
        graph[0].append(i + 1)
        for j in range(m):
            if needs[i][j] > 0:
                graph[i + 1].append(n + j + 1)

    # 初始化拓扑排序状态
    in_degree = [0] * (n + m + 1)
    for i in range(1, n + m + 1):
        for j in range(len(graph[i])):
            in_degree[graph[i][j]] += 1

    # 执行拓扑排序
    order = []
    queue = [0]
    while len(queue) > 0:
        u = queue.pop(0)
        order.append(u)
        for v in graph[u]:
            in_degree[v] -= 1
            if in_degree[v] == 0:
                queue.append(v)

    # 判断是否有环
    if len(order) != n + m + 1:
        return False, []

    # 计算顺序
    windows = [0] * (n + 1)
    ans = []
    for i in range(1, n + 1):
        cur_max = 0
        for j in range(1, len(graph[order[i]])):
            k = graph[order[i]][j] - n
            cur_max = max(cur_max, windows[k] + needs[order[i] - 1][k - 1])
        windows[order[i]] = cur_max
        ans.append(order[i] - 1)

    if len(ans) != n:
        return False, []
    else:
        return True, ans
复杂度分析

设 n 为门的数量,m 为材料的数量。

  • 时间复杂度:$O(nm)$。构建有向图的时间复杂度为 $O(nm)$,拓扑排序的时间复杂度为 $O(n+m)$,计算顺序的时间复杂度也为 $O(nm)$,因此总时间复杂度为 $O(nm)$。
  • 空间复杂度:$O(nm)$。算法中使用了一个 $n+m+1$ 个元素的列表来存储有向图中的节点信息,因此空间复杂度为 $O(nm)$。