📜  门|门 CS 1997 |问题 25(1)

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

题目介绍:门|门 CS 1997 | 问题 25

题目描述

题目链接:https://www.luogu.com.cn/problem/P1179

给定一个大小为 $n$ 的二维平面,平面上有 $m$ 个门和 $k$ 个障碍物,其中每个门可以通向一个确定的位置,每个障碍物为一个点。同时设定一个起始点,问从起始点出发,是否能够经过所有的门,且路程尽量短。如果能够经过,输出需要行动的最短路程,否则输出不可行。

输入格式

第一行输入三个参数 $n, m, k$,其中 $n$ 表示平面的边长,$m$ 表示平面中的门的数量,$k$ 表示平面中的障碍物数量。

接下来 $m$ 行,每行包含 $3$ 个整数 $x, y, z$,表示一个门的坐标 $(x, y)$ 和该门通向的位置,通向的位置坐标从 $1$ 到 $n^2$。

接下来 $k$ 行,每行包含两个整数 $x, y$,表示一个障碍物的坐标 $(x, y)$。

最后一行,包含两个整数 $x, y$,表示起始点的坐标。

输出格式

输出只有一行,如果存在一条可以经过所有门的路径,则输出该路径的最短距离,否则输出 NO。

输入样例

3 2 2 2 2 1 3 3 3 1 1 1 3 3 1 2 2

输出样例

10

算法1:Dijkstra 算法
思路

因为随着路程的增加,可能会经过之前经过过的门,所以无法使用简单的贪心思路求解。这里使用 Dijkstra 算法求解最短路程。

将起始点入队,然后从队列中出队一个点作为当前点,再将该点顶点所连接的边加入到优先级队列中。每次取出距离最短的边进行更新,直到队列为空,输出最短距离。

因为门不仅包含其位置,同时还包含目的地位置,所以我们需要将该门的位置和目的地位置都加入到邻接表中。

另外,因为门是双向通行的,所以我们需要在邻接表中加上反向路径。

代码实现
import heapq


class Edge:
    def __init__(self, to, cost):
        self.to = to
        self.cost = cost


def dijkstra(s, edges):
    q = []
    heapq.heappush(q, (0, s))
    d = [float('inf')] * (n * n + m)
    d[s] = 0

    while q:
        p = heapq.heappop(q)
        v = p[1]
        if d[v] < p[0]:
            continue
        for e in edges[v]:
            if d[e.to] > d[v] + e.cost:
                d[e.to] = d[v] + e.cost
                heapq.heappush(q, (d[e.to], e.to))
    return d


n, m, k = map(int, input().split())
edges = [[] for _ in range(n * n + m)]
w = [list(map(int, input().split())) for _ in range(m)]
for i in range(m):
    x, y, z = w[i]
    edges[(x - 1) * n + y].append(Edge(z + n * n - 1, 1))
    edges[z + n * n - 1].append(Edge((x - 1) * n + y, 1))
obstacle = [tuple(map(int, input().split())) for _ in range(k)]
start = tuple(map(int, input().split()))

for i, j in obstacle:
    for k2 in range(m):
        if w[k2][0] == i and w[k2][1] == j:
            break
    else:
        edges[(i - 1) * n + j].append(Edge(k2 + n * n - 1, 1))
        edges[k2 + n * n - 1].append(Edge((i - 1) * n + j, 1))

for i in range(m):
    x, y, z = w[i]
    for j in range(m):
        if i == j:
            continue
        x_, y_, z_ = w[j]
        dist = abs(x - x_) + abs(y - y_)
        edges[i + n * n].append(Edge(j + n * n, dist))
        edges[j + n * n].append(Edge(i + n * n, dist))

for i in range(m):
    x, y, z = w[i]
    for x_ in range(1, n + 1):
        dist = abs(x - x_) + abs(y - 1)
        if dist == 0:
            continue
        edges[i + n * n].append(Edge((x_ - 1) * n, dist))
        edges[(x_ - 1) * n].append(Edge(i + n * n, dist))

    for y_ in range(1, n + 1):
        dist = abs(x - 1) + abs(y - y_)
        if dist == 0:
            continue
        edges[i + n * n].append(Edge(y_ - 1, dist))
        edges[y_ - 1].append(Edge(i + n * n, dist))

d = dijkstra((start[0] - 1) * n + start[1], edges)
ans = float('inf')
for i in range(m):
    ans = min(ans, d[i + n * n])
if ans == float('inf'):
    print("NO")
else:
    print(ans)
复杂度分析

Dijkstra 算法时间复杂度为 $O((m + n^2) \log{(m + n^2)})$,因为需要建立邻接表,邻接表建立的时间复杂度为 $O(m + n^2)$,空间复杂度为 $O(m + n^2)$。因此,程序的总时间复杂度为 $O((m + n^2) \log{(m + n^2)})$,总空间复杂度为 $O(m + n^2)$。

总结

本题虽然题目的表述显得较为复杂,但其本质为求从起始点出发,经过所有门并且路程尽量短的问题。可以使用 Dijkstra 算法求解本问题。