📜  门|门 IT 2008 |第 48 题(1)

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

门|门 IT 2008 第 48 题

这是一道经典的算法题,考察的是图论中的最短路径问题,也是一道比较基础的题目。

题目描述

给定一个有向无环图,节点编号从 1 到 n,每个节点有一个权值 w[i],表示从起点到该节点的路径上会经过 w[i] 个门。现在你可以在任意门前停留,但是只能以 w[i] 的倍数时间前进。问从起点到终点的最短时间是多少?

输入格式

第一行两个整数 n 和 m,表示节点数和边数。

接下来 m 行,每行三个整数 a, b, c,表示存在一条从 a 到 b 的有向边,边的长度为 c。

最后一行 n 个整数,依次表示每个节点的 w 值。

输出格式

输出一个整数,表示从起点到终点的最短时间。

解题思路

首先,这是一道图论中的最短路径问题,因此可以使用 Dijkstra 算法或者 Bellman-Ford 算法来求解。由于这是有向无环图,可以使用拓扑排序对节点进行排序,对于每个节点 u,依次遍历它的出边,进行松弛操作即可。时间复杂度为 O(m+n)。

其次,需要注意的是,节点上的权值 w[i] 表示从起点到该节点的路径上会经过 w[i] 个门,因此需要根据 w 值将节点分成若干个等价类,类内的节点可以互相到达,且经过门的数量相同。对于同一等价类内的节点,它们的最短路径长度是相同的。因此,遍历节点的时候,应该按照等价类的顺序进行遍历,这样可以确保遍历到某个节点时,它的前面的节点已经更新过了。

代码实现

以下代码使用了拓扑排序和 Dijkstra 算法,时间复杂度为 O(m+n)。

from collections import defaultdict
import heapq

def topo_sort(graph):
    # 拓扑排序
    in_degrees = {node: 0 for node in graph}
    for _, neighbors in graph.items():
        for v in neighbors:
            in_degrees[v] += 1

    queue = [node for node, in_degree in in_degrees.items() if in_degree == 0]
    order = []
    while queue:
        node = queue.pop(0)
        order.append(node)
        for v in graph[node]:
            in_degrees[v] -= 1
            if in_degrees[v] == 0:
                queue.append(v)

    return order

def dijkstra(graph, start, weights):
    # Dijkstra 算法
    heap = [(0, start)]
    visited = set()
    dist = {node: float('inf') for node in graph}
    dist[start] = 0

    while heap:
        (d, node) = heapq.heappop(heap)
        if node in visited:
            continue
        visited.add(node)

        for neighbor, weight in graph[node]:
            w = weights[neighbor-1]
            if dist[node] % w != 0:
                edge_len = w - dist[node] % w
            else:
                edge_len = w
            if dist[node] + edge_len + weight < dist[neighbor]:
                dist[neighbor] = dist[node] + edge_len + weight
                heapq.heappush(heap, (dist[neighbor], neighbor))

    return dist

n, m = map(int, input().split())

graph = defaultdict(list)
for _ in range(m):
    a, b, c = map(int, input().split())
    graph[a].append((b, c))

weights = list(map(int, input().split()))

order = topo_sort(graph)

dist = {node: float('inf') for node in graph}
dist[1] = 0
for node in order:
    if dist[node] == float('inf'):
        continue
    for neighbor, weight in graph[node]:
        w = weights[neighbor-1]
        if dist[node] % w != 0:
            edge_len = w - dist[node] % w
        else:
            edge_len = w
        if dist[node] + edge_len + weight < dist[neighbor]:
            dist[neighbor] = dist[node] + edge_len + weight

print(dist[n])
结语

本题虽然是一道经典的算法题,但是它其实还是有一定难度的。需要注意图中存在等价类这个条件,并且需要根据等价类的顺序来遍历节点。同时,还需要注意时间的计算,需要根据 w 值来计算经过门的数量。