📜  门| GATE-CS-2014-(Set-3) |第 59 题(1)

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

门 | GATE-CS-2014-(Set-3) |第 59 题

本题是计算机科学门类历年考试中的一道题目,涉及图论与逻辑运算。

题目描述

给定一个拓扑有序的图 $G = (V, E)$,其顶点标号为 $1, 2, …, n$,边集 $E$ 中的元素 $(u, v)$ 表示顶点 $u$ 在拓扑上在 $v$ 前面。图中有 $2$ 个特殊的顶点:起始节点 $s$ 和终止节点 $t$。钦定 $s$ 在拓扑上在 $t$ 前面。将图中顶点按其拓扑顺序编号,设起始节点 $s$ 的顶点编号为 $1$,终止节点 $t$ 的顶点编号为 $n$。在这个拓扑排序下,路径 $(s, t)$ 中第 $i$ 个顶点编号为 $p_i$,如果 $(p_i , p_{i+1}) \in E$ 则有一个门,基本门的造价为 $1$,存在一些特殊的门,他的造价为 $c$,这些门的位置在 $i_1, i_2, …, i_k$。通过找一条路径,令路径上的所有门的造价之和最小。

编写一个函数 int findCost(int n, int* cost, int s, int t, int k, int* gates),其中输入参数为:

  • n: 顶点数。
  • cost: 一个长度为 $n$ 的数组,表示每个门的造价,cost[1] 到 cost[k] 分别为 $i_1, i_2, …, i_k$ 上的特殊门的造价,cost[j] 为普通门的造价。
  • s: 起始点的编号。
  • t: 目标点的编号。
  • k: 特殊门的数量。
  • gates: 一个长度为 $k$ 的数组,其中 gates[i] 表示第 $i$ 个特殊门的位置 $i_i$。

输出一个整数,表示满足条件的路径的最小成本。

示例输入输出
输入

n = 6, cost[] = {0, 3, 4, 2, 5, 1, 8}, s = 1, t = 6, k = 2, gates[] = {3, 5}

输出

8

解释

有两个特殊门,分别在第 $3$ 和第 $5$ 个点,造价分别为 $2$ 和 $1$,寻找路径 $(1, 2, 3, 4, 5, 6)$,这条路径经过两个特殊点,因此总成本应该为 $2 + 1 = 3$,但是存在路径 $(1, 2, 4, 5, 6)$,总成本为 $5 + 1 = 6$,显然更优。因此输出结果为 $8$。

解题思路

通过深度优先搜索或广度优先搜索遍历所有节点,并记录当前到达节点时的路径上所经过的特殊门,以及对应的成本和。然后比较不同路径的成本之和,取最小值即可。

伪代码如下:

visited = set()
path = []
min_cost = float('inf')

# 定义深度优先搜索函数
def dfs(node, total_cost, has_gate):
    if node in visited:
        return
    
    if node == t:
        if total_cost < min_cost:
            min_cost = total_cost
        return
    
    visited.add(node)
    path.append(node)
    
    for neighbor in graph[node]:
        if neighbor not in visited:
            if neighbor in gates:
                dfs(neighbor, total_cost + cost[gates.index(neighbor)], True)
            else:
                if has_gate:
                    dfs(neighbor, total_cost + cost[-1], True) # cost[-1] 为普通门的造价
                else:
                    dfs(neighbor, total_cost, False)
                    
    visited.remove(node)
    path.pop()

另外,我们也可以使用 Dijkstra 算法或者 Bellman-Ford 算法来解决这道题目。

对于 Dijkstra 算法,我们可以将图中不同的边权分为两种,一种是普通门,权值为 $c$,另外一种是特殊门,权值为每个特殊门所在节点的权值。在算法的过程中,我们使用一个最小堆来维护哪些节点的路径成本已经被确定,然后用一个最小堆,找到所有与这些节点相邻的节点的路径成本,更新堆中的信息即可。

对于 Bellman-Ford 算法,我们可以先将所有的节点的路径成本都设置为正无穷大,将出发点的路径成本设置为 $0$,然后依次遍历每条边,更新所有节点的路径成本。这个算法的好处是相对简单,但是效率稍低。

代码实现
import heapq

def dijkstra(graph, cost, source, dest, gates):
    dist = [float('inf')] * len(graph)
    dist[source] = 0
    
    q = []
    heapq.heappush(q, (0, source, 0))
    
    while q:
        curr_dist, curr_node, has_gate = heapq.heappop(q)
        
        if curr_node == dest:
            return curr_dist
        
        if dist[curr_node] < curr_dist:
            continue
        
        for neighbor in graph[curr_node]:
            if neighbor in gates:
                dist_cost = curr_dist + cost[gates.index(neighbor)]
                heapq.heappush(q, (dist_cost, neighbor, True))
            else:
                dist_cost = curr_dist + cost[-1] if has_gate else curr_dist
                heapq.heappush(q, (dist_cost, neighbor, False))
                
            if dist[neighbor] > dist_cost:
                dist[neighbor] = dist_cost
                
    return -1

def findCost(n, cost, s, t, k, gates):
    graph = [[] for _ in range(n)]
    for i in range(n):
        if i > 0:
            graph[i].append(i - 1)
        if i < n - 1:
            graph[i].append(i + 1)
            
    return dijkstra(graph, cost, s-1, t-1, [x-1 for x in gates] if k else [])

上面的代码实现使用了 Dijkstra 算法来解决这个问题,使用了 Python 内置的 heapq 库来实现最小堆。

总结

本题是一道典型的图论题目,要求我们通过搜索或者更高效的图算法来寻找一条路径,使得路径中的所有特殊门成本之和最小。本题中比较容易出现的错误包括:

  • 首先,不注意审题,或者没有充分理解题目所描述的图。
  • 其次,编写的搜索过程过于复杂,或者算法的时间复杂度高得不能通过测试用例。
  • 第三,没有充分考虑特殊门和普通门对于路径成本的影响。